Java 学习
老李 Lv4

java 基础

访问修饰符

修饰符 同一个类 同一个包 子类 整体
private
default
protected
public

方法重写

  • 重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型。
  • 重写方法不能使用比被重写方法更严格的访问权限。
  • 重写方法不能抛出比被重写方法范围更大的异常类型。

super

  • super 可用于访问父类中定义的属性
  • super 可用于调用父类中定义的成员方法
  • super 可用于在子类构造方法中调用父类的构造方法
  • super 的追溯不仅限于直接父类

调用父类构造方法

  • 在子类的构造方法中可使用super(参数列表)语句调用父类的构造方法
  • 如果子类的构造方法中没有显示地调用父类构造方法,也没有使用this关键字调用重载的其它构造方法,则系统默认调用父类无参数的构造方法
  • 如果子类构造方法中既未显式调用父类构造方法,而父类中又没有无参的构造方法,则编译出错

子类对象的实例化过程

子类对象的实例化过程

多态性

多态 — 在Java中,子类的对象可以替代父类的对象使用

  • 一个变量只能有一种确定的数据类型
  • 一个引用类型变量可能指向(引用)多种不同类型的对象
    1
    2
    3
    Person p = new Student();
    Object o = new Person(); // Object类型的变量o,指向Person类型的对象
    o = new Student(); // Object类型的变量o,指向Student类型的对象

父类类型的变量可以指向子类的对象

一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法

1
2
3
4
Student m = new Student();
m.school = "pku"; // 合法, Student 类有 school 成员变量
Person e = new Student();
e.school = "pku"; // 非法, Person 类没有 school 成员变量

属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。

虚拟方法调用

  • 正常的方法调用
    1
    2
    3
    4
    Person e = new Person();
    e.getInfo();
    Student e = new Student();
    e.getInfo();
  • 虚拟方法调用(多态情况下)
    1
    2
    Person e = new Student();
    e.getInfo(); // 调用 Student 类的 getInfo() 方法

    编译时类型和运行时类型

      编译时 e 为 Person 类型,而方法的调用是在运行时确定的,所以调用的是 Student 类的 getInfo() 方法。—— 动态绑定

对象类型转换

基本数据类型的类型转换

  • 小的数据类型可以自动转换成大的数据类型
  • 可以把大的数据类型强制转换成小的数据类型

Java对象的强制类型转换称为造型

  • 从子类到父类的类型转换可以自动进行
  • 从父类到子类的类型转换必须通过造型(强制类型转换)实现
  • 无继承关系的引用类型间的转换是非法的
  • 在造型前可以使用 instanceof 操作符测试一个对象的类型

== 操作符与 equals 方法

== 操作符

  • 引用类型比较引用(是否指向同一个对象)
1
2
3
4
5
Person p1 = new Person();
Person p2 = new Person();
if (p1 == p2) {
// TODO
}
  • 基本类型比较值
1
2
3
4
int a = 5;
if (a == 6) {
// TODO
}

用”==”进行比较时,符号两边的数据类型必须一致(可自动转换的基本数据类型除外),否则编译出错

equals()方法是Object类的方法,由于所有类都继承Object类,也就继承了equals()方法。只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。格式:obj1.equals(obj2)
特例:当用equals()方法进行比较时,对类File、String、Date及封装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
原因:在这些类中覆盖了equals()方法。

多线程

合理利用线程池能够带来三个好处

  1. 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

杂记

将指定 class 文件打包成 jar

1
2
3
jar cvf jobserver.jar com\bonc\usdp\jobserver\job\IJobServerDataflowJob.class \
com\bonc\usdp\jobserver\job\IJobServerSiimpleJob.class \
com\bonc\usdp\jobserver\job\JobserverContext.class

使用 java 命令运行代码

1
2
3
4
5
java -jar <jar-file-name>.jar
如果 jar 里没有 MANIFEST.MF :
java -cp foo.jar full.package.name.ClassName
如果 main 类依赖多个 jar ,可以把多个jar打包到一个目录,然后用 -Djava.ext.dirs 指定该目录,引用依赖的多个 jar (如果用 -cp 则需要写每一个 jar ):
java -Djava.ext.dirs=lib full.package.name.ClassName

抽象类和抽象接口之间的区别

那么抽象类和抽象接口之间的区别是什么呢?它们不都能包含抽象方法和包含方法体的实现吗?
首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。
其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。

菱形继承问题

如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。
(1) 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
(2) 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
(3) 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。

取模和与操作

1
h % n == h & (n - 1)

相较于取模运算, 与运算效率更高。

JDK 8 中 HashMap 就是计算 index 时就是这么计算的。