java基础

CH5 继承

5.1 类、超类和子类

  1. 反射(reflection):在程序运行期间发现发现更多的类及其属性的能力。
  2. 关键字 this 和 super
    this用法:

    1.引用隐式参数

    2.调用该类其他的构造器

    super用法:

    1.调用超类的方法

    2.调用超类的构造器 。调用构造器的语句只能作为另一个构造器的第一条语句出现。

    public Manager(String name, double salary, int year, int month, int day)
    {
    super(name, salary, year, month, day);
    bonus = 0;
    }
  3. 强制类型转换

​ ClassCastException 类抛出异常、类型强制转换异常

​ 子类可以强转为超类,超类不能强转为子类,向上兼容

​ 在进行强转之前,要先查看是否可以强转成功:

if(staff[1] instanceof Manager){
boss = (Manager) staff[1];
...
}
  1. java不支持多继承,支持多层继承
  2. override 覆盖/重写 🆚 overload 重载

    Override:是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。

    overload:是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。

    方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  3. 关键字 final

    final 类:此类不能被继承

    final 方法:此方法不能被子类覆盖

    final 变量:必须进行初始化操作

    将一个类声明为final,只有其中的方法自动变成final,而不包括其中的域。

    Sting类是final类

  4. 抽象类 abstract

    抽象类可以包含具体方法、抽象方法

    不能被实例化,但可以创建具体子类对象

    abstract Person()
    {
    public String name;
    public Person(Stirng name)
    {
    this.name = name;
    }
    }

    Student extends Person()
    {...}

    new Person("Tom"); //❌
    Person p = new Student("Tom","Economics");//✅
  5. java中的4个访问修饰符

    1. private —— 仅对本类可见

    2. public —— 对所有类可见

    3. protected —— 对本包和所有子类可见

    4. 默认、不需要修饰符 —— 对本包可见

      最好将类中的域标记为private,而将方法标记为public

      将域或方法声明为protected,使得超类中某些方法允许被子类访问,或允许子类的方法访问超类的某个域

5.2 Object:所有类的超类

  1. equals方法

    用于检测一个对象是否等于另外一个对象,object类中的equals方法是用来比较首地址的,等同于==。String中的equals方法被重写用于比较字符串中所包含的内容是否相同。

    如果是基本类型比较,那么只能用==来比较,不能用equals

    == 与 equals() 的区别

    1.对于字符串变量、基本类型的包装类型,比如Boolean、Character、Byte、Shot、Integer、Long、Float、Double等的引用变量:(推荐用equals)

    ==:比较两个变量的对象在内存中的首地址。

​ equals():比较字符串中所包含的内容是否相同。

​ 2.对于非字符串变量:

​ ==与equals用法相同,用于比较对象值

  1. hashCode方法

    hashCode 散列码:由对象导出的一个没有规律的正、负整型值

    但字符串的散列码是由内容导出的,所以内容相同的字符串的hashCode也相同。

    • 重写equals()必须重写hashCode()
    • 哈希存储结构中,添加元素重复性校验的标准就是先检查hashCode值,后判断equals()

    • 两个对象equals()相等,hashcode()必定相等

    • 两个对象hashcode()不等,equals()必定也不等
    • 两个对象hashcode()相等,对象不一定相等,需要通过equals()进一步判断。

    对于数组类型的域,可以使用静态的Arrays.hashCode方法计算散列码

  2. toString 方法

​ 用于返回表示对象值的字符串;只要对象与一个字符串通过操作符 “+” 相连接,java编译就会自动调用toString方法。

​ 在调用x.toString()的地方可以用 “”+x 来替代,且如果x是基本类型,这条语句照样能执行。

​ object类定义了toString方法,用来打印输出对象所属的类名@散列码,为了直观显示,要自己重写toString方法。

​ 打印一维数组可以调用静态方法Arrays.toString方法

​ 打印多维数组可以调用Arrays.deepToString方法

5.3 泛型数组列表 ArrayList<>

1.访问数组列表元素

​ java允许在运行时确定数组的大小。

​ ArrayList是一个采用类型参数(type parameter)的泛型类(generic class)。它被称作菱形语法。

//声明和构造一个保存100个Empolyee对象的数组列表
ArrayList<Employee> staff = new ArrayList<Employee>(100);
//在java SE7中,可以省去右边的类型参数
ArrayList<Employee> staff = new ArrayList<>(100);
//数组容量还可以用ensureCapacity方法来设置
staff.ensureCapacity(100);
//size方法将返回数组列表中包含的实际元素数目,相当于数组a.length
staff.size();
//一旦确定数组列表的大小不再发生变化,就可以调用trimToSize方法,此方法将调整存储区域大小为当前元素数目所需要的存储空间
staff.trimToSize();
//添加元素到数组列表 add
staff.add(new Employee("Tom",7500,1987,12,15));
//在数组列表中间插入元素,使用带参数的add
staff.add(n,e);
//在数组列表中间删除一个元素
staff.remove(n);
//访问数组列表元素
staff.get(n);
//修改数组列表元素 set
staff.set(n,("Jerry",7500,1987,12,15));
//用toArray将数组元素拷贝到一个数组中
ArrayList<X> list = new ArrayList<>();
X a[] = new X[list.size()];
list.toArray(a);

2.类型化与原始数组列表的兼容性

//对于没有使用类型参数的旧代码:
public class EmployeeDB
{
public void update(ArrayList list){...}
public ArrayList find(String query){...}
}
//可以把类型化ArrayList赋给一个原始ArrayList
ArrayList<Employee> staff = ...
employeeDB.update(staff);
//把原始ArrayList赋给类型化ArrayList会得到一个警告
ArrayList<Employee> result = employeeDB.find(query);//⚠️yields warning
//即使强制类型转换,也会被警告
ArrayList<Employee> result = ArrayList<Employee> employeeDB.find(query);
//可以用@SuppressWarnings("unchecked")标注来标记这个变量可以接受类型转换
@SuppressWarnings("unchecked")
ArrayList<Employee> result = ArrayList<Employee> employeeDB.find(query);//yield another warning

5.4 对象包装器与自动装箱

​ final 对象包装器类(wrapper):Integer、Long、Float、Double、Short、Byte、Character、Void、Boolean。不允许更改包装在其中的值,不能定义子类。(wrapper是final类)

​ ArrayList 的效率远远低于int[],应该用它来构造小型集合,当对操作方便性的需要高于执行效率时。

​ 自动装箱(autoboxing):把int值转换为Integer对象

​ 自动拆箱(Auto-unboxing):把Integer对象转换为int值

5.5参数数量可变的方法

​ Object… args,表示这个方法可以接收任意数量的参数

​ 允许将数组传递给可变参数方法的最后一个参数,例如:

System.out.printf("%d %s",new Object[]{new Integer(1),"widgets"});

5.6 枚举类 enum

​ 在比较两个枚举类型的值时,直接使用==,不要用equals。

​ 枚举类中域声明为private final,保证安全。

​ 所有的枚举类型都是Enum类的子类。

public enum Size
{
//()内是枚举常量的参数,必须创建对应参数的构造器
SMALL("s"),
MEDIUM("M"),
LARGE("L");

private final String abbreviation;

//构造器
private Size(String abbreviation)
{
this.abbreviation = abbreviation;
}
public String getAbbreviation()
{
return abbreviation;
}
}

5.7 反射 reflec

​ 能够分析类能力的程序称为反射。

​ 反射机制作用:

​ 1. 在运行时分析类的能力

​ 2.在运行时查看对象,例如,编写一个toString类供所有类使用

​ 3.实现通用的数组操作代码

​ 4.利用Method对象,这个对象类似C++中的函数指针

  1. Class 类

    定义:Java虚拟机为每一个java对象维护它在运行时的类型标识信息,这些信息跟踪着每个对象所属的类,保存这些信息的类被称为Class。


    虚拟机为每个类型管理一个Class对象,可用 == 来比较两个类对象,例如

    if(e.getClass() == Employee.class) ...

    newInstance()方法:动态创建一个类的实例,并调用默认无参构造器初始化它,如果这个类没有默认构造器,就会抛出一个异常

    forName()和newInstance() 配合,根据类名创建一个对象

    Object obj = Class.forName("Employee").newInstance();

    Class类中的getFields、getMethods、getConstructors方法分别返回类提供的public域,方法和构造器数组,其中包括超类的公有成员。安全性低。

    Class类中的getDeclaredFields、getDeclaredMethods、getDeclaredConstructors方法分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。安全性高


    对公有域、私有域的访问控制,需要调用Field、Method、Constructors对象的setAccessible方法,例如

    Field f = Class.forName("Employee").getDeclaredFields("salary");
    f.setAccessible(true);

    获得Class类对象的4种方法:

    方法一:

    Object类中的getClass()方法将会返回一个Class 类型的实例。

    最常用的Class方法是getName,这个方法将返回类的名字。如果类在一个包里,包的名字也作为类名的一部分。

    方法二:

    调用静态方法Class.forName(“classpath”)获得类名对应的Class对象。

    使用此方法最好提供一个异常处理器,因为只有在classname是类名或者接口名时才能执行,否则将抛出CheckedException(已检查异常)。

    方法三:

    如果T是任意的java类型(或void关键字),T.class 可代表匹配的类对象

    方法四:

    通过类加载器xxxClassLoader.loadClass()传入类路径获取,通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行

    //方法一
    e.getClass().getName();
    //方法二
    Class cl = Class.forName("cn.javaguide.TargetObject");
    //方法三
    e.class
    //方法四
    Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
  1. 调用任意方法

    类似利用Field类的get方法查看对象域的过程,在Method方法中有一个invoke方法,它允许调用包装在当前Method对象中的方法。

    getMethod方法的签名:Method getMethod(String name, Class… parameterTypes)

    invoke方法的签名: Object invoke(Object obj, Object… args)

  1. 继承的设计技巧

​ 1.将公共操作和域放在超类

​ 2.不要使用受保护的域

​ 3.使用继承实现“is-a”关系

​ 4.除非所有继承的方法都有意义,否则不要使用继承

​ 5.在覆盖方法时,不要改变预期的行为

​ 6.使用多态,而非类型信息

​ 7.不要过多地使用反射,这种功能对于编写系统来说极其实用,但是通常不适用于编写应用程序

框架与反射

注解本质是一个继承了Annotation 的特殊接口

CH6 接口、lambda表达式与内部类

6.1 接口

​ 定义:接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

​ 要点:

​ 1.接口中所有方法自动被设置为public,所有域被自动设置为public static final,建议不要书写这些多余的关键字

​ 2.在实现接口时,必须把方法声明为public,否则编译器就会把这个方法的访问属性视作包可见性

​ 3.在接口中不能含有实例域,但是可以定义常量

​ 4.允许在接口中增加静态方法(尽管这有违于将接口作为抽象规范的初衷)

​ 5.可以为接口提供一个默认实现,这个默认方法需要用default修饰符来标记

​ 6.instanceof可以检查一个对象是否继承了某个类,也可以检查一个对象是否实现了某个接口

​ 7.每个类只能继承一个超类,却可以实现多个接口。Java不支持多继承,但支持多实现。接口可以提供多继承的许多好处,同时还能避免多继承带来的使语言变得更复杂或更低效的问题。虽然C++具有多继承特性,但很少有C++程序员适用多继承。

​ 8.解决默认方法冲突的2条原则:

​ 1.超类优先:当超类与接口中的相同签名的默认方法发生冲突时,超类优先

​ 2.接口冲突:当不同接口中的相同默认方法发生冲突时,根据提示手动写代码选择


隐式参数:是在类的方法中调用了类的实例域。这个被调用的实例域就是隐式参数。在隐式参数的前面加上this,隐式参数就更清晰,this操作符代表的是本类。

回调(callback):在某个特定事件发生时应该采取的动作

lambda表达式:一个可传递的代码块,可以在以后执行一次或多次。特点:延迟执行。

在lambda表达式中不能引用会变化的变量,不能改变变量的值,不能在lambda表达式中声明与局部变量同名的参数或局部变量。

(【类型】【参数】,,)->{

​ 【代码块】

}

函数式接口:对于只有一个抽象方法的接口,需要这种接口的对象时,可以提供一个lambda表达式。这种接口称为函数式接口。

用::方法引用:

用::分隔方法名与对象或类名,主要有3种情况:

object::intstanceMethod

class::staticMethod

class::instanceMethod

用::构造器引用:

java无法构造泛型类型T的的数组,数组构造器引用

//构造Person对象数组
Object[] people = stream.toArray();//得到一个object引用数组
Person[] people = stream.toArray(Person[]::new);

6.4 内部类

内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域

(因为内部类的对象总有一个指向创建它的外部类隐式引用)。

只有内部类可以被声明为私有。

内部类中声明的所有静态域都必须是final

内部类不能有static方法,除了静态内部类。

局部内部类:在一个方法中定义局部类

特点:

可以对外部世界完全地隐藏起来,

匿名内部类:没有类名,

特点:匿名内部类没有构造器。

通常的语法格式:

new Supertype(construction parameters){

Methods and data

}

java中实现事件监听和其他回调:使用匿名内部类、lambda表达式

静态内部类:取消内部类对外围类的引用;可以在其中定义静态域、静态方法

使用场景:不需要内部类引用外围类的对象

异常

Java异常类层次结构图

Java异常类层次结构图

必须处理受查异常,否则程序无法运行

可以不处理非受查异常

try-catch-finally
  • try块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch块: 用于处理 try 捕获到的异常。
  • finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

在以下 3 种特殊情况下,finally 块不会被执行:

  1. tryfinally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
  2. 程序所在的线程死亡。
  3. 关闭 CPU。

注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。

引用拷贝、浅拷贝、深拷贝

截屏2022-01-17 下午2.32.33

引用拷贝:没有在内存中创建新对象,而是直接创建一个引用指向被拷贝对象

浅拷贝:在内存中创建了新对象,但对原对象的成员变量没有完全在内存中开辟新空间,对于原对象的基础类型变量开辟了新空间,但对原对象的引用类型没有开辟新空间,只是创建了一个新的引用

深拷贝:在内存中创建了新对象,并对其所有类型包括引用类型的成员变量在内存中都开辟了新空间

序列化

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。

序列化

transient 关键字

对于不想进行序列化的变量,使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

关于 transient 还有几点注意:

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。

获取用键盘输入常用的两种方法

方法 1:通过 Scanner

方法 2:通过 BufferedReader

Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

按操作方式分类结构图:

IO-操作方式分类

按操作对象分类结构图:

IO-操作对象分类

字节流和字符流

音频文件、图片等媒体文件用字节流比较好,字符的话使用字符流比较好

中文在 utf-8 编码下占用了 3 个字节