Java基础复习
1.Java异常
1.1异常概念
- 若某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。而此时该方法会立刻退出同时不返回任何值。另外,调用该方法的其他代码也无法继续执行,异常处理机制也会将代码执行交给异常处理器
- 也就是说,当程序中的方法遇到某个问题时,会抛出一个封装好的错误信息异常并立刻退出,同时会阻止其他代码继续执行,并将处理权交给异常处理器
1.2异常Throwable分类
1.2.1Error类
- 指的是Java虚拟机无法解决的问题,比如JVM内部错误、资源耗尽。
- 应用程序不会抛出该类对象,若出现了这样错误,会将错误信息告知给用户,然后让程序停止运行
1.2.2Exception类
1.2.2.1RuntimeException运行时异常
- 如NullPointerException空指针异常、IndexOutOfBoundsException下标越界异常等,这些异常都是不检查异常,在程序中可以选择捕获处理,也可以不处理。
- 一般由程序逻辑错误引起,程序应该从逻辑角度尽可能地避免这类异常地发生
1.2.2.2CheckedException检查异常
- 从程序角度来看,是必须进行处理的异常;若不处理,则程序就不能编译通过,如IOException、ClassNotFoundException等、用户自定义的Exception异常。
- 一般是外部错误,该异常通常发生在编译阶段,Java编译器会强制程序去捕获此类异常,一般包括
- 试图在文件尾部读取数据
- 试图打开一个错误格式的URL
- 试图根据给定的字符串查找class对象,而这个字符串表示的类并不存在
1.3异常的处理
- 抛出异常有三种形式,一种是throw,一种是throws,以及虚拟机默认处理的异常
- 针对异常进行处理的方式
- try…catch
- try…catch…finally
- try…finally
- try:可能会发生的异常
- catch:针对异常进行处理的代码
- finally:释放资源代码
1.3.1try/catch/finally的注意事项
-
try代码后必须有catch块或finally块,且try块中代码越少越好
-
catch不能单独存在,且catch块中得有执行的代码内容
-
finally块中的代码一定会执行
-
try…catch块后不是必须要添加finally代码块
-
try / catch / finally块间不能添加任何代码
-
若存在多个catch,且存在上下级,则最上级异常需要放在最后面
-
捕获范围小的异常要放在捕获范围大的异常之前处理
1.3.2throw / throws的使用
-
throw:在方法中认为抛出一个异常对象,该异常对象可以是自定义的异常,也可以是已存在的异常
(用在方法内)
-
throws:在方法的声明中使用,使用该方法时必须处理该异常
(用在方法上)
1.3.4自定义异常
- 自定义异常类用来扩展内置的异常类。主要表示特定于自己的应用程序领域的异常情况
// 自定义异常类
class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
//抛出自定义异常
throw new MyException("This is my exception.");
2.Java反射
2.1动态语言
- 指程序在运行时可以改变其结构,新的函数可以引进,已有的函数可以被删除等结构上的变化
2.2反射概念
- 指在程序运行状态下,对于任意一个类都能知道该类的所有属性和方法,并且对于任意一个对象,都能够调用它的任意一个方法,也就是具有能动态获取信息以及动态调用对象方法的功能
- Java中的反射就是将Java类中的各种成分映射成一个个的Java对象
- 反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法
2.3反射应用场景
-
Java程序中许多对象在运行时会出现两种类型:编译时类型和运行时类型
-
编译时类型:由声明对象时实用的类型来决定
-
运行时类型:由实际赋值给对象的类型来决定
-
例:
Person p = new Student();
编译时类型为Person,运行时类型为Student
- 如果在程序编译时无法预知该对象和类属于哪些类,则程序只能依靠运行时的信息来发现该对象和类的真实信息,就需要用到反射
2.4反射API
-
java.lang.Class:反射的核心类,Class对象可以获取某个类加载后在堆中的对象及对象属性
获取Class对象的两种方式:
- 对象.getClass():获取读写所属类对应的Class对象
- 类名.class:获取类名对应的Class对象
-
java.lang.reflect.Field:反射类的成员变量,Field 对象可以获取/设置某个类的成员变量
获取Field对象的方式:
Class.getDeclaredField()
方法获取所有的字段对象Field.get()
方法获取字段的值。field.setAccessible(true)
字段若是私有的,需要设置可访问性
-
java.lang.reflect.Method:反射类的方法,Method 对象可以获取某个类的方法信息
获取Method对象的两种方式:
- Class.getMethod(String name,Class… parameterTypes):获取指定方法名和参数类型的public方法
- Class.getDeclaredMethod(String name,Class… parameterTypes):获取指定方法名和参数类型的任意方法
-
java.lang.reflect.Constructor:反射类的构造方法,Constructor 对象可以获取某个类的构造器
获取Constructio类的两种方式:
- Class.getConstructor(Class… parameterTypes):获取指定参数类型的public构造器。
- Class.getDeclaredConstructor(Class… parameterTypes):获取指定参数类型的任意构造器。
2.5获取类信息
- 获取类名
- Class.getName()
- Class.getSimpleName()
- 获取父类
- Class.getSuperclass()
- 获取接口
- Class.getInterfaces()
- 获取类属性
- Class.getDeclaredFields()
- 获取类方法
- Class.getDeclaredMethods()
- 获取类构造函数
- Class.getDeclaredConstructors()
2.6创建对象
- 类创建对象,使用Class.newInstance()方法
- 构造方法创建对象,使用Construction.newInstance()方法
2.7访问属性
- 获取属性,使用Class.getDeclaredField()和field.get()
- 设置属性,使用Class.getDeclaredField()和field.set()
2.8调用方法
- 调用无参方法,使用Class.getMethod()和method.invoke();
- 调用有参方法,使用Class.getMethod()和method.invoke(参数值);
2.9获取泛型
-
获取类的泛型参数,使用Class.getTypeParameters();
-
获取方法的泛型参数,使用Class.getMethod()和method.getTypeParameters();
-
获取属性的泛型参数,使用Class.getDeclaredField()和field.getGenericType()和parameterizedType.getActualTypeArguments()
3.Java注解
3.1注解概念
- 是放在Java源码的类、方法、字段、参数前的特殊注释
- 注解本质是一个继承了Annotation的特殊接口,使用也叫声明式接口
3.2注解分类
3.2.1Java自带的标准注解
- @Override:用于表示一个方法是重写父类中的方法
- @Deprecated:用于标记已经过时的方法或类,提醒开发者不再使用
- @SuppressWarnings:用于抑制编译器产生的警告信息
- @FunctionalInterface:用于标记一个接口是函数式接口,即只包含一个抽象方法的接口
- @SafeVarargs:用于消除关于可变参数方法的警告
3.2.2元注解
- 用于定义注解的注解,也是Java自带的标准注解
-
@Retention定义了Annotation的生命周期
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢掉 RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中 RetentionPolicy.RUNTIME 注解可以保留到程序运行中的时候,它会被加载进 JVM 中,在程序运行中也可以获取到它们 -
@Documented用于制作文档,将注解中的元素包含到doc中
-
@Target指定了注解可以修饰哪些地方
ElementType.ANNOTATION_TYPE 给一个注解进行注解 ElementType.CONSTRUCTOR 给构造方法进行注解 ElementType.FIELD 给属性进行注解 ElementType.LOCAL_VARIABLE 给局部变量进行注解 ElementType.METHOD 给方法进行注解 ElementType.PACKAGE 给包进行注解 ElementType.PARAMETER 给一个方法内的参数进行注解 ElementType.TYPE 给一个类型进行注解,比如类、接口、枚举 -
@Inherited指定该注解可以被其子类继承
- @Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效
- @Inherited 不是表明 注解可以继承,而是子类可以继承父类的注解
-
@Repeatable用来声明该注解是可重复的
3.2.3自定义注解
- 根据自己的需求定义注解
自定义注解其步骤:
-
新建注解文件,@interface定义注解
public @interface MyAnnotate{}
-
添加参数、默认值
public @interface MyAnnotate{ String name( ) default ""; int value( ) default 0; }
-
用元注解配置注解
@Retention(RetionPolicy.RUNTIME) @Target(Element.TYPE) public @interface MyAnnotate{ String name( ) default ""; int value( ) default 0; }
4.Java内部类
4.1静态内部类
- 定义在类内部的静态类,就是静态内部类
public class Outer{
private static int a;
private int b;
public static class Inner{
public void print(){
System.out.println(a);
}
}
}
-
静态内部类可以访问外部类所有的静态变量和方法,即使是private也能访问到
-
静态内部类和正常的类一样,可以定义静态变量、方法、构造方法等
-
其他类使用静态内部类需要使用
外部类.静态内部类
外部类名.内部类名 变量名=new 外部类名.内部类();
Outer.Inner inner = new Outer.Inner(); inner.print();
-
Java集合类HashMap内部有一个静态内部类Entry,Entry是HashMap存放元素的抽象,HashMap内部维护Entry数组用了存放元素,但是Entry对使用者是透明的。
像这种和外部类关系密切的,且不依赖外部实例的,都可以使用静态内部类
4.2成员内部类
- 定义在类内部的非静态类,就是成员内部类。成员内部类不能定义静态方法和变量(final 修饰的除外),因为成员内部类是非静态的
- 类初始化的时候先初始化静态成员,如果允许成员内部类定义静态变量,那么成员内部类的静态变量初始化顺序是有歧义的
public class Outer{
private static int a;
private int b;
public class Inner{
public void print(){
System.out.println(a);
System.out.println(b);
}
}
}
-
成员内部类时,语法格式:
-
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
Outer.Inner inner = new Outer().new Inner();
-
外部类名 变量名1 = new 外部类名();
外部类名.内部类名 变量名2 = 变量名1.new 内部类名();
-
-
当内部类的变量名和外部类的变量名相同时,优先访问自己的,而想调用外部类的成员,调用方法:外部类名.this.同名成员
-
外部类的任何成员都可以在成员内部类方法中直接访问
4.3局部内部类
- 在方法中定义的内部类
class Outer {
private static int a;
private int b;
public void test(final int c) {
final int d =1;
class Inner {
public void print() {
System.out.println(c);
}
}
//实例化局部内部类
Inner inner = new Inner();
inner.print();
}
}
- 只能在该方法体内部使用,其他位置都不能使用
- 也不能被访问修饰符public、private、default修饰
4.4匿名内部类
- 没有名字的内部类,只能使用一次,通常是某个类在实现时只会使用一次,为了简化,不需要专门写一个实现类,而是直接使用内部类完成
public class TestClass {
public void Test(Bird bird){
System.out.println(bird.getName()+"能飞"+bird.fly()+"米");
}
public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.Test(new Bird() {
@Override
public int fly() {
return 1000;
}
public String getName(){
return "麻雀";
}
});
}
}
abstract class Bird{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public abstract int fly();
}
-
匿名内部类必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。
且没有class关键字,因为匿名内部类是直接使用new来生成一个对象的引用
-
匿名内部类不能定义任何静态成员、方法、类,只能创建匿名内部类的一个实例
匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类
-
匿名内部类不能有构造方法,且不能定义任何静态成员、静态方法
-
匿名内部类不能是public,protected,private,static
5.Java泛型
5.1泛型概念
- 泛型的本质是类型参数化,利用泛型可以实现一套代码对多种数据类型的动态代理,保证了更好的代码重用性
- 泛型还提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
5.2泛型集合
- 泛型在集合中对数据元素的类型进行限定,集合作为一个容器主要用来容纳保存数据元素,但是在不同的场景中,集合中保存的类型的对象也不一样,所以就需要要求集合有一个很好的通用性
public class TestCollection {
public static void main(String[] args) {
//定义集合泛型
//这里定义的是一个只接受String类型的ArrayList集合
ArrayList<String> list = new ArrayList<>();
// list.add(100);
list.add("Hello World");
System.out.println(list);
//这里定义的是一个只接受Integer类型的HashSet集合
HashSet<Integer> set = new HashSet<>();
// set.add("Hello World");
set.add(100);
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()){
Integer next = iterator.next();
System.out.println(next);
}
//这里定义的是一个限定Map集合的key是String类型,value是Integer类型
HashMap<String, Integer> map = new HashMap<>();
// map.put(11111,"value");
map.put("value",11111);
Integer value = map.get("value");
System.out.println(value);
}
5.3泛型方法
-
指具有泛型类型参数的方法,通过使用泛型方法,可以在方法级别上使用类型参数,使方法能够处理不同类型的数据,提高代码的灵活性和复用性
-
普通泛型方法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { … }
-
静态泛型方法
方法限定符 static <类型形参列表> 返回值类型 方法名称(形参列表) { … }
public static <E> void printArray(E[] inputArray){
for (E e : inputArray) {
System.out.printf("%s",e);
}
}
5.4泛型类
- 除了在类名后面需要添加类型参数声明部分,且参数间用逗号分隔。一个泛型参数被称为一个类型变量,用于指定一个泛型类型名称的标识符。因为接受一个或多个参数,所以被称为参数化的类或参数化的类型
- 定义一个泛型类引用:泛型类 <类型实参> 变量名;
- 实例化一个泛型类对象:new 泛型类 <类型实参> (构造方法实参);
class Person<T>{
private T t;
public void add(T t){
this.t=t;
}
public T get(){
return t;
}
}
5.5泛型接口
-
interface Factory<T> { public int makeProduct(T t); }
-
定义一个泛型接口:interface 泛型接口名<类型形参列表> {…}
5.6通配符
5.6.1上限通配符<? extends T>
-
是一种表示类型上限的通配符,其中T是一个类或接口,泛型类的类型必须实现或继承T这个接口或类
指定了可以使用的类型上限,主要是用于限制输入的参数类型
-
<? extends T>表示该通配符所代表的类型是T类型的子类
5.6.2下限通配符<?super T>
-
是一种表示类型下限的通配符,其中T是一个类或接口,指定了可以使用的类型下限,主要是用于限制输出的参数类型
-
<? super T>表示该通配符所代表的类型是T类型的父类
5.6.3未限定通配符
-
是一种表示未知类型的通配符,可以在需要一个类型参数的情况下使用。但由于没有限制,只能用于简单的情况
-
public static void print(List<?> data) { for(int i=0;i<data.size();i++) { //getSimpleName():用于获取某个类的类名 System.out.println(data.getClass().getSimpleName()+"--data: " + data.get(i)); } }
5.7泛型擦除
-
在编译阶段,编译器会检查泛型类型的类型安全性,但在运行阶段,由于泛型类型参数被擦除了,就无法保证类型安全性
-
泛型擦除的基本过程
首先找到用来替换类型参数的具体类,该具体类一般是Object,若指定了类型参数的上限,则使用该上限,然后将代码中的类型参数都替换成具体的类
-
主要表现在以下:
- 无法使用基本类型实例化类型参数
- 无法在运行时获取泛型类型信息
- 泛型类型参数不能用于静态变量或静态方法
- 不能实例化T类型
6.Java序列化/反序列化
- Java对象的序列化和反序列化是一种将对象转换成字节流并存储在硬盘或网络中,以及从字节流中重新加载对象的操作
6.1序列化概念
- 是将Java对象转换成字节流的过程,可用于持久化数据,传输数据等。序列化后的字节流可以被传输给远程系统,并在哪里重新构造成原始对象
- Java序列化是一个将对象转化为字节流的过程
6.2反序列化概念
-
是将字节流重新恢复为原始对象的过程,是对象序列化的逆过程
通过反序列化操作能够在接收端恢复出与发送端相同的对象
-
Java反序列化是一个将字节流转化为对象的过程
6.3应用场景
- 持久化对象
- 使用RMI(远程方法调用)
- 在网络中传递对象
6.4Java序列化实现
- 实现对象的序列化,最直接的操作就是实现Serializable接口
- 通过ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化
- 在类中增加writeObject和readObject方法可以实现自定义序列化策略
6.4.1实现Serializable接口
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
6.4.2通过IO流进行序列化/反序列化
public static void main(String[] args) {
// 序列化
try {
Person person = new Person("张三", 20);
FileOutputStream fileOut = new FileOutputStream("person.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person);
out.close();
fileOut.close();
System.out.println("Person对象已经被序列化并保存到person.txt文件中");
} catch (IOException i) {
i.printStackTrace();
}
// 反序列化
try {
FileInputStream fileIn = new FileInputStream("person.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
Person person = (Person) in.readObject();
in.close();
fileIn.close();
System.out.println("从person.txt文件中反序列化出来的Person对象:");
System.out.println("Name: " + person.getName());
System.out.println("Age: " + person.getAge());
} catch (IOException i) {
i.printStackTrace();
} catch (ClassNotFoundException c) {
System.out.println("Person类未找到");
c.printStackTrace();
}
}
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
6.4.3增加writeObject/readObject方法实现
- Externalizable继承自Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
Person person1 = new Person("John", 30, "California");
//写入文件
FileOutputStream fileOut = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(person1);
fileOut.close();
//通过文件读取
FileInputStream fileInput = new FileInputStream("person.ser");
ObjectInputStream input = new ObjectInputStream(fileInput);
Person person2 = (Person) input.readObject();
fileInput.close();
System.out.println(person2.getName());
System.out.println(person2.getAge());
System.out.println(person2.getAddress());
}
class Person implements Externalizable {
private String name;
private int age;
private String address;
public Person() {
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
out.writeObject(address);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name=(String)in.readObject();
age=in.readInt();
address=(String)in.readObject();
}
}
6.5序列化ID
- 当实体类中没有显式的定义个名为serialVersionUID,类型为Long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,而只有同一次编译生成的class才会生成相同的serialVersionUID。
- 若出现需求变动,Bean类发生改变,则会导致反序列化失败,为了不出现该问题,最好还是显式的指定serialVersionUID
private static final long serialVersionUID = 1L;
6.6Transient关键字
- 在变量声明前加上Transient关键字,可以阻止该变量被序列化到文件中,在被反序列化后,Transient变量的值被设为初始值
- 服务器端在给客户端发送序列化对象数据,对象中有一些数据字段是敏感的,希望对数据字段在序列化时进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全
private transient 类型 变量名;
6.7注意事项
- 成员变量必须可序列化,静态变量不会被序列化
- transient关键字可避免被序列化
- 当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口
- 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化
7.Java复制
7.1直接赋值复制
- 直接赋值复制是将一个对象的引用赋给另一个对象,使它们指向同一块内存地址,修改其中一个对象的属性将会影响到另一个对象
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
Person person1 = new Person("张三", 30, "China");
//直接赋值复制
Person person2 = person1;
System.out.println(person2.getName());
System.out.println(person2.getAge());
System.out.println(person2.getAddress());
System.out.println("-------------------");
person2.setName("李四");
System.out.println(person2.getName());
System.out.println(person2.getAge());
System.out.println(person2.getAddress());
}
class Person {
private String name;
private int age;
private String address;
public Person() {
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
7.2浅复制
- 浅复制是通过复制对象的引用和基本数据类型的值来创建一个新对象,新对象和原对象共享相同的引用类型属性,但是修改其中一个对象的引用类型属性将不会影响到另一个对象
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("张三", 30, "China");
//浅复制
Person person2 = (Person) person1.clone();
System.out.println(person1);
System.out.println(person2);
System.out.println("---------------------");
person1.setName("李四");
person1.setAge(22);
System.out.println(person1);
System.out.println(person2);
}
class Person implements Cloneable{
private String name;
private int age;
private String address;
public Person() {
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
7.3深复制
- 深复制是通过复制对象的引用和基本数据类型的值来创建一个新对象,新对象和原对象完全独立,修改任意一个对象的属性都不会影响到另一个对象
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("张三", 30, "China");
//深复制
Person person2 = (Person) person1.clone();
System.out.println(person1);
System.out.println(person2);
System.out.println("---------------------");
person1.setName("李四");
person1.setAge(22);
System.out.println(person1);
System.out.println(person2);
}
class Person implements Cloneable{
private String name;
private int age;
private String address;
public Person() {
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Person clone() throws CloneNotSupportedException {
Person person=(Person) super.clone();
return person;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
7.4序列化
- 通过将对象序列化到字节流中,然后再将其从字节流中读出来,这样产生的新对象是对现有对象的一个深拷贝,且两个对象之间也不存在引用共享的问题,实现对象的深拷贝
public static void main(String[] args) throws Exception {
Person person1 = new Person("张三", 30, "China");
//序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.ser"));
objectOutputStream.writeObject(person1);
objectOutputStream.close();
//反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.ser"));
Person person2 = (Person) objectInputStream.readObject();
objectInputStream.close();
System.out.println(person1);
System.out.println(person2);
}
}
class Person implements Serializable {
private String name;
private int age;
private String address;
public Person() {
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
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;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
8.抽象类和接口
8.1抽象类概念
- 抽象类(Abstract Class)是一个用
abstract
关键字定义的类,可以包含抽象方法和非抽象方法。抽象类不能被实例化,只能被继承,并且子类必须实现抽象类中的抽象方法。
8.2接口概念
- 接口(Interface)是一个用
interface
关键字定义的结构,可以包含抽象方法和默认方法(自 Java 8 引入)。接口不能被实例化,只能被实现。
8.3继承和实现
- 类可以继承一个抽象类来获得它的属性和方法,并可以拓展、重写或覆盖它们。一个类只能继承一个抽象类。
- 类可以实现多个接口,从而获得接口中定义的抽象方法。实现接口时,必须提供接口中定义的所有抽象方法的实现。
8.4成员变量及方法
-
抽象类可以有实例变量、静态变量和常量。
-
接口只能有常量(
public static final
)。
- 抽象类中的非抽象方法可以有具体的实现。
- 接口中的方法都是抽象的,没有具体的实现。在 Java 8 中,接口引入了默认方法和静态方法,可以提供默认的实现。
8.5应用场景
- 抽象类适用于那些有共同属性和行为的类的继承关系,它们之间存在一种"is-a"的关系。抽象类可以作为其他类的基类,提供通用的方法和属性,同时允许子类进行特殊化。
- 接口适用于定义多个类之间的共同行为,它们之间存在一种"has-a"的关系。接口定义了一组方法签名,实现这些接口的类需要提供具体的实现。通过实现接口,类可以具有多态性。
9.Java集合
- Iterator:作为集合的输出接口,主要用于遍历输出Collection集合中的元素,也被称为迭代器。迭代器接口是集合接口的父接口,实现类实现Collection时就必须实现Iterator接口
- Collection:是List、Set、Queue的父接口,是存放一组单值的最大接口。单值是指集合中的每个元素都是一个对象
- List:是有序可重复集合,能够使用索引来精确的访问List中的元素
- Queue:是Java提供的队列集合
- Set:是无序不可重复集合,只能根据元素本身来访问Set中的元素
- Map:是存储Key-Value键值对的集合,根据元素对应的Key来访问其Value
9.1Collection接口
Collection在接口中定义了一系列操作集合元素的方法
方法 | 作用 |
---|---|
add(Object obj) | 向集合增加元素 |
addAll(Collection coll) | 添加一个集合的全部元素 |
int size() | 获取有效元素个数 |
void clear() | 清空集合 |
boolean isEmpty() | 是否为空集合 |
boolean contains(Object obj) | 通过元素的equals方法来判断是否是同一个对象 |
boolean containsAll(Collection c) | 调用元素的equals方法来比较的。用两个集合的元素逐一比较 |
boolean remove(Object obj) | 通过元素的equals方法判断是否是要删除的那个元素。 只会删除找到的第一个元素 |
boolean removeAll(Collection coll) | 取当前集合的差集 |
boolean retainAll(Collection c) | 把交集的结果存在当前的集合中,不影响c |
boolean equals(Object obj) | 集合是否相等 |
Object [] toArray() | 转换成对象数组 |
hashCode() | 获取集合对象的哈希值 |
iterator() | 返回迭代器对象,用于集合遍历 |
9.1.1迭代器的使用
//获取迭代器对象
Iterator iterator = 集合.iterator();
//判断索引下一个是否还有元素
while(iterator.hasNext()){
Object next = iterator.next();
System.out.println("next="+next);
}
- 若iterator.next()指向的内存没有元素则会抛出异常
- 若iterator.next()指向最后一个元素,继续执行会报错
Iterator迭代器原理
Iterator itarator = collection.iterator();
得到一个集合的迭代器
hasNext()
判断是否还有下一个元素
while(iterator.hasNext()){
next()
作用:1.下移获取后面的元素,2. 将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
重置迭代器:iterator=collection.iterator();
在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测,若不调用,且下一条记录无效,直接调用iterator.next()会抛出
9.1.2循环遍历
//迭代器遍历集合
1.先得到collection对应的迭代器
Iterator iterator = collection.Iterator();
2.使用while循环遍历
while(iterator.hasNext()){
//返回下一个元素,类型是Object
Object obj=iterator.next();
System.out.println("obj=",obj);
}
-------------------------------------------------------------------------------
//增强for循环遍历集合
for(Object obj : collection){
System.out.println("obj=",obj);
}
-------------------------------------------------------------------------------
//普通for循环遍历集合
for(int i=0;i<list.size();i++){
//Object object=list.get(i);
//System.out.println(object);
System.out.println(list.get(i));
}
9.2List接口
9.2.1List接口特点
- List集合类中的元素有序,可重复,集合中的每个元素都有对应的顺序索引,索引从0开始
- List接口底层中以数组的方法存放元素,允许存放null元素
- list容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
9.2.2List接口常用方法
方法 | 作用 |
---|---|
void add(int index, Object ele) | 在index位置插入ele元素 |
boolean addAll(int index, Collection eles) | 从index位置开始将eles中的所有元素添加进来 |
Object get(int index) | 获取指定index位置的元素 |
int indexOf(Object obj) | 返回obj在集合中首次出现的位置 |
int lastIndexOf(Object obj) | 返回obj在当前集合中末次出现的位置 |
Object remove(int index) | 移除指定index位置(0是第一个元素)的元素,并返回此元素 |
Object set(int index, Object ele) | 设置指定index位置的元素为ele |
9.2.3List接口遍历方式
-
普通for循环遍历
for(int i=0;i<list.size();i++){ System.out.println("list="+list.get(i)); }
-
增强for循环遍历
for (Object o :list) { System.out.println("o="+o); }
-
Iterator迭代器遍历
Iterator iterator=list.iterator(); while(iterator.hasNext()){ Object next=iterator.next(); System.out.println("next="+next); }
9.2.4ArrayList集合
- ArrayList底层是用数组来保存对象的,用自动扩容的机制实现动态增加容量,因为底层是用数组实现,所以插入和删除操作效率不佳,不建议用ArrayList做大量增删操作,因为其本身有索引,所以查询效率很高,适合做大量查询操作
- ArrayList():构造一个默认大小为10容量的空列表
- ArrayList(int initialCapacity):构造一个大小为指定int initialCapacity容量的空列表
- ArrayList(Collection c):构造一个和参数c相同元素的ArrayList对象
JDK7:直接创建一个初始容量为10的数组
JDK8:一开始创建一个长度为0的数组,当添加第一个元素时再创建一个容量为10的数组。当数组超过初始容量时,接下来每次扩容都是前一次容量+前一次容量的二分之一
9.2.5LinkedList集合
- LinkedList类采用链表结构保存对象,便于向集合中插入或者删除元素,所以适合做大量增删操作。当频繁向集合中插入和删除元素时,效率要高于ArrayList类,LinkedList类访问元素的速度则比较慢
- LinkedList实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
方法 | 作用 |
---|---|
void addFirst(E e) | 将指定元素添加到此集合的开头 |
void addLast(E e) | 将指定元素添加到此集合的末尾 |
E getFirst() | 返回此集合的第一个元素 |
E getLast() | 返回此集合的最后一个元素 |
E removeFirst() | 删除此集合中的第一个元素 |
E removeLast() | 删除此集合中的最后一个元素 |
- LinkedList底层维护了一个双向链表,通过两个属性first和last分别指向首节点和尾结点。
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。
9.2.6Vector集合
- Vector底层是用数组来保存对象的,用自动扩容的机制实现动态增加容量,是线程安全的,意味着即使在多线程环境中使用而不会产生竞态条件
- Vector 是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 当需要线程同步安全时,考虑Vector
- 在Vector中定义了动态数组,用于存储集合中的元素,在添加元素时,若集合的大小达到了当前数组的长度,则会将数组扩容为原来的2倍。在删除元素时,若元素的数量小于数组长度的一般,则会将数组缩小为原来的一半
- Vector默认的存储大小为10,不指定自动扩容大小,则默认使用当前数组大小进行自加扩容(直接相加或者构造方法中指定扩容大小);
9.3Set接口
9.3.1Set接口特点
- Set集合中元素是无序(存入和取出的顺序不一定相同)的,同时也是不可重复的,可以添加null
- 可以使用迭代器,增强for循环,不能使用索引的方式来获取元素
9.3.2Set接口常用方法
与List接口同属Collection接口,所以常用方法类似
9.3.3Set接口遍历方式
使用迭代器遍历
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Object obj=iterator.next();
System.out.println("obj=",obj);
}
----------------------------------------------------------
使用增强for循环
for(Object obj : set){
System.out.println("obj=",obj);
}
9.3.4HashSet集合
- HashSet是为了优化查询速度而设计的Set,允许存放null值。元素不可重复且无序
- 对于存在HashSet集合中的元素需要重写equals()方法和HashCode()方法
- HashSet是线程不安全的
添加元素
-
添加一个元素时,先得到hash值,会转成索引值
-
找到存放数据表table,看这个索引的位置是否已经存放有元素
-
若没有,就将元素加入该索引位置
-
若有,则调用equals比较(区别于正常的equals方法,这里的equals方法可以自定义判断)
若相同,就放弃添加,若不相同,则以链表的形式添加到最后
-
在java8中,若一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),
并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
扩容机制
-
HashSet的底层是HashMap,第一次添加时,table数组扩容到16,
临界值(threshold)是16 * 加载因子(loadFactor)是0.75=12
-
若table数组使用到了临界值12,就会扩容到16*2=32,新的临界值就是32*0.75=24,以此类推
9.3.5LinkedHashSet集合
- LinkedHashSet是HashSet的子类,是无序,且无索引,并且不允许添加重复元素
- LinkedHashSet的底层是一个LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使得元素看起来是以插入顺序保存的
- 在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet 有head和tail)
- 每个结点有pre和next属性,这样可以形成双向链表
- 在添加元素时,先求hash值,在求索引,确定该元素在hashtable的位置,然后将添加的元素加入到双向链表(若已经存在,则不添加(原则和hashset一样))
tail.next=newElement
newElement.pre=tail
tail=newElement;
- 当在遍历LinkedHashSet也能确保插入顺和遍历顺序一致
9.3.6TreeSet集合
- 底层数据结构采用二叉树来实现,元素唯一且已经排好序,唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。
- 按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
compareTo比较方法
TreeSet treeset=new TreeSet(new Comparator(){
@Override
public int compare(Object o1,Object o2){
//调用String的compareTo方法进行字符串大小比较
//若出现key字符串相同的情况,是添加不进去元素的
return ((String) o2).compareTo((String) o1);
//按照长度大小排序
//若出现key长度相同的情况 是添加不进去元素的,但是可以替换value
return (((String) o2).length()).compareTo(((String) o1).length());
}
});
9.4Queue接口
9.4.1Queue接口概念
- Queue用于模拟队列这种数据结构,队列通常是指“先进先出”(FIFO)的容器。
- 队列的头部是保存在队列中存放时间最长的元素,队列的尾部是保存在队列中存放时间最短的元素。
- 新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。
9.4.2Queue接口常用方法
方法 | 作用 |
---|---|
void add(Object e) | 将指定元素加入此队列的尾部 |
Object element() | 获取队列头部的元素,但是不删除该元素。 |
boolean offer(Object e) | 将指定元素加入此队列的尾部。 当使用有容量限制的队列时,此方法通常比 add(Object e)方法更好 |
Object peek() | 获取队列头部的元素,但是不删除该元素。如果此队列为空,则返回null |
Object poll() | 获取队列头部的元素,并删除该元素。如果此队列为空,则返回null |
Object remove() | 获取队列头部的元素,并删除该元素 |
9.4.3PriorityQueue集合
- PriorityQueue 是一个比较标准的队列实现类。
- PriorityQueue 保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。
- 自然排序:要求队列元素必须实现Comparable接口,而且应该是同一个类的多个实例
- 定制顺序:创建PriorityQueue队列时,传入Comparator对象对队列中的所有元素进行排序,不要求队列元素实现Comparator接口。
9.4.4Deque接口与ArrayDequeu实现类
Deque的双端队列常用方法
方法 | 作用 |
---|---|
void addFirst(Object e) | 将指定元素插入该双端队列的开头 |
void addLast(Object e) | 将指定元素插入该双端队列的末尾 |
Iterator descendingIterator() | 返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素 |
Object getFirst() | 获取但不删除双端队列的第一个元素 |
Object getLast() | 获取但不删除刷过段队列的最后一个元素 |
Boolean offerFirst(Object e) | 将指定元素插入该双端队列的开头 |
Boolean offerLast(Object e) | 将指定元素插入该双端队列的末尾 |
Object peekFirst() | 获取但不删除该双端队列的第一个元素,若双端队列为空,则返回null |
Object peekLast() | 获取但不删除该双端队列的最后一个元素,若双端队列为空,则返回null |
Object pollFirst() | 获取并删除该双端队列的第一个元素,若双端队列为空,则返回null |
Object pollLast() | 获取并删除该双端队列的最后一个元素,若双端队列为空,则返回null |
Object pop() | pop出该双端队列所表示的栈的栈顶元素 |
void push(Object e) | 将一个元素push进该双端队列所表示的栈的栈顶,相当于addFirst(e) |
Object removeFirst() | 获取并删除该双端队列的第一个元素 |
Object removeFIrstOccurrence(Object o) | 删除该双端队列的第一次出现的元素o |
Object removeLast() | 获取并删除该双端队列的最后一个元素 |
boolean removeLastOccurrence(Object o) | 删除该双端队列的最后一次出现的元素o |
ArrayDeque
- ArrayList和ArrayDeque两个集合类的实现机制基本相似,它们的底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出了该数组的容量时,系统会在底层重新分配一个Object[]数组来存储集合元素
9.5Map接口
9.5.1Map接口特点及常用方法
- Map和Collection 并列存在,用于保存具有映射关系的数据 :key - value
- Map中的key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map的key不允许重复,但是value可以重复
- Map的key可以为null,value也可以为null,注意key 为null,只能有一个,value为null,可以为多个
- 常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
方法 | 作用 |
---|---|
void clear() | 删除该 Map 对象中的所有 key-value 对。 |
boolean containsKey(Object key) | 查询 Map 中是否包含指定的 key,如果包含则返回 true。 |
boolean containsValue(Object value) | 查询 Map 中是否包含一个或多个 value,如果包含则返回 true。 |
V get(Object key) | 返回 Map 集合中指定键对象所对应的值。V 表示值的数据类型 |
V put(K key, V value) | 向 Map 集合中添加键-值对,如果当前 Map 中已有一个与该 key 相等的 key-value 对,则新的 key-value 对会覆盖原来的 key-value 对。 |
void putAll(Map m) | 将指定 Map 中的 key-value 对复制到本 Map 中。 |
V remove(Object key) | 从 Map 集合中删除 key 对应的键-值对,返回 key 对应的 value,如果该 key 不存在,则返回 null |
boolean remove(Object key, Object value) | Java8新增的方法,删除指定 key、value 所对应的 key-value 对。如果从该 Map 中成功地删除该 key-value 对,该方法返回 true,否则返回 false。 |
Set entrySet() | 返回 Map 集合中所有键-值对的 Set 集合,此 Set 集合中元素的数据类型为 Map.Entry |
Set keySet() | 返回 Map 集合中所有键对象的 Set 集合 |
boolean isEmpty() | 查询该 Map 是否为空(即不包含任何 key-value 对),如果为空则返回 true。 |
int size() | 返回该 Map 里 key-value 对的个数 |
Collection values() | 返回该 Map 里所有 value 组成的 Collection |
9.5.2Map遍历集合
- containsKey:查找键是否存在
- keySet:获取所有的键
- entrySet:获取所有的关系
- values:获取所有的值
第一种
//先取出所有的key,通过key获取对应的value
Set keyset=map.KeySet();
//增强for
for(Object key : keyset){
System.out.println(key +"-"+ map.get(key));
}
第二种
Set keyset=map.KeySet();
//迭代器
Iterator iterator = keyset.Iterator();
while(iterator.hasNext()){
Object key=iterator.next();
System.out.println(key +"-"+ map.get(key));
}
第三种
//把所有的values值取出来
Collection values =map.values();
//可以使用Collection使用的遍历方法
//增强for
for(Object value : values){
System.out.println(value);
}
第四种
Collection values =map.values();
//迭代器
Iterator iterator=values.iteartor();
while(iterator.hasNext()){
Object obj=iteartor.next();
System.out.println(value);
}
第五种
//通过EntrySet来获取 key-value
set entrySet=map.entrySet();
//增强for
for(Object entry : entrySet){
//将entry 转成 Map.Entry
Map.Entry m=(map.Entry) entry;
System.out.println(m.getKey() +"-"+ m.getValue());
}
第六种
set entrySet=map.entrySet();
//迭代器
Iterator iterator = entrySet.Iterator();
while(iterator.hasNext()){
//HashMap$Node 实现了 Map.Entry(getKey,getValue)
Object entry=iterator.next();
//向下转型 相当于Map.Entry调用了getKey()和getValue()方法
Map.Entry m=(Map.Entry) entry;
System.out.println(m.getKey() +"-"+ m.getValue());
}
9.5.3HashMap集合
- HashMap是Map接口使用的频率最高的实现类
- HashMap是以key-value对的形式来存储数据**(HashMap$Node类型)**
- key不能重复,但是值可以重复,允许使用null键和null值
- 若添加相同的key,则会覆盖原来的key-value 等同于修改(key不会替换,value会替换)
- 不保证映射的顺序,因为底层采用hash表的方式来存储
- HashMap没有实现同步,因此线程是不安全的
扩容机制
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加key-value 时,通过key的哈希值得到在table的索引,然后判断该索引处是否有元素,
若没有元素则直接添加,若该索引处有元素,继续判断该元素的key是否和准备加入的key相等
若相等,则直接替换value,若不相等,则需要判断是树结构还是链表结构,做出相应的处理。
若添加时发现容量不够,则需要扩容
- 第1次添加,则需要扩容table容量为16,临界值(threshold)为12 (16 * 0.75)
- 之后再次扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,以此类推
- 在java8中,若一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=
MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
9.5.4HashTable集合
- 存放的元素是键值对:即Key-Value
- hashtable的键和值都不能为null,否则会抛出NullPointerException
- hashtable是线程安全的,hashmap不是线程安全的
- 底层数组Hashtable$Entry[] 初始化大小为 11
- 临界值 threshold 8 =11*0.75
- 执行方法 addEntry(hash,key,value,index); 添加key-value 封装到Entry
- 当if (count >= threshold) 满足时,就进行扩容
- 按照int newCapacity=(oldCapacity <<1 )+1 ;的大小扩容
9.5.5TreeMap集合
- 不允许元素重复、无索引下标、可默认排序或自定义排序(按照键数据的大小默认升序(有小到大)排序。只能对键排序。)
- TreeMap跟TreeSet一样底层原理是一样的。
TreeMap treemap=new TreeMap(new Comparator(){
@Override
public int compare(Object o1,Object o2){
//调用String的compareTo方法进行字符串大小比较
//若出现key字符串相同的情况,是添加不进去元素的
//return ((String) o2).compareTo((String) o1);
//按照长度大小排序
//若出现key的长度相同的情况 是添加不进去元素的,但是可以替换value
return ((String) o2).length() - ((String) o1).length();
}
});
9.5.6Properties集合
- 继承自Hashtable类并且实现了Map接口,使用键值对的形式来保存数据
- 可以用于从 xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
- 不能存放空键和空值,key和value不能为空
方法 | 作用 |
---|---|
String getProperty(String key) | 获取Properties中键值为key的属性值 |
String getProperty(String s1,String s2) | 获取Properties中键为s1的属性值,若不存在s1的值,则获取键为s2的值 |
Object setProperty(String key,String value) | 设置属性值,类似于Map的put()方法 |
void load(InputStream inputStream) | 从属性中获取所有的键值对,将加载到的属性追加到properties里,不保证加载顺序 |
void store(OutputStream out,String s) | 将Properties中的键值对输出到指定文件中 |
9.5.7LinkedHashMap集合
-
有序(保证存储和取出的元素顺序一致)、不允许元素重复、无索引下标。
-
底层数据结构是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的迭代顺序。该迭代顺序可以是插入顺序或者是访问顺序。
根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。
默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。