本文将系统地梳理面向对象Java的所有知识点
Lecture1
面向对象
- 将实际问题分解为不同的对象
- 不同的对象提供不同的服务
- 对象之间可以传递消息
面向对象语言
- 编程语言直接提供了对对象的支持
- 定义对象 , 对象提供的服务 , 消息传递方法
- 优点
- 缩短实际问题到计算机算法的距离
面向对象编程要素
- 任何事物都是对象
- 程序为一些对象之间的相互协作
- 一个对象可以包含另一个对象
- 每个对象都有类型
- 一种类型的对象接收相同类型的消息,提供相同类型的服务
对象的基本要素
- 状态
- 行为
- 类型
利用已有类型定义新的类型
- 复用
- 组合
- 继承
Lecture2
面向对象编程的基本步骤
- 定义类型
- 创建属于该类型的对象
- 使用对象的服务
定义Java类
class MyType{
//数据
int i;
double d;
char c;
//方法
void set(double d);
double get();
}
创建类的对象
MyType a = new MyType();
使用对象的方法
int b = a.i;
a.set(0.5);
a.get();
销毁对象
- Java语言会自动销毁对象
引用
- 引用是受限的指针
- 体现在不允许使用指针运算,以及无法强制类型转换
不可变类型
- 类型的对象一旦创建就不能被改变
- 例子 String 类 , Integer 类 , Float 类 ...
对象存储位置
- 基本类型存储在栈内存
- 类存放在堆内存
Lecture3
强制转换操作
- 基本类型
- 自动转换 : 当转换是安全的 ( 例如 int 转为 double)
- 显式转换 : 当转换将损失精度 ( 例如 double 转 int)
- boolean 类型 不能强制转换
- 类
- 一般不允许强制转换(特殊情况下的继承类可以)
循环结构foreach
int [ ]a = {1, 2, 3, 4, 5};
for (int i : a)
System.out.println(i);
静态方法
- 定义时加关键字static
public class MyType { int i; double d; char c; void set(double x) { d = x; } double get() { return d; } //静态方法 public static void main(String [ ]args) { System.out.println(“Hello”); } }
- 不用创建对象就可以被调用的方法
- 也称为:类方法
静态数据
- 类似于静态方法,不需要实例化即可被调用
- 可以在类的不同对象中共享
默认访问控制
- 方法默认private
- 接口默认public
库
- 一组功能相关的类,为其他用户提供服务
模块化编程
- 将任务分解成为简单 , 更容易管理的子任务
- 优点
-
- 简单
-
- 易于debug
-
- 代码重用
-
- 易于维护
Lecture4
函数重载
- 根据参数的类型和数量确定方法,而非函数名
- 优点:接口简洁统一
- 返回值无法重载
自动装箱/拆箱
考虑如下代码
//在-128~127 之外的数
Integer i1 =200;
Integer i2 =200;
System.out.println("i1==i2: "+(i1==i2));
// 在-128~127 之内的数
Integer i3 =100;
Integer i4 =100;
System.out.println("i3==i4: "+(i3==i4));
输出
i1==i2: false
i3==i4: true
Integer.java源码
public static Integer valueOf(int i) {
if(i >= -128 && i <= 127)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
对于–128到127(默认是127)之间的值,Integer.valueOf(int i) 返回的是缓存的Integer对象(并不是新建对象)
所以范例中,i3 与 i4实际上是指向同一个对象。
而其他值,执行Integer.valueOf(int i) 返回的是一个新建的 Integer对象,所以范例中,i1与i2 指向的是不同的对象。
当然,当不使用自动装箱功能的时候,情况与普通类对象一样。
this关键字
- 在非静态方法中,返回调用该方法对象的引用
- 特殊情况,调用构造函数
public class MyType { int i; double d; char c; void set(double x) { d = x; } double get() { return d; } MyType(double d) {this.d = d;} MyType(int i) {this.i = i;} MyType(int i, double d, char c){ this(d); this.i = i; // can not use this(i) again this.c = c; } public static void main(String [ ]args) { MyType m = new MyType(); m.set(1); } }
- 出现在构造函数第一行
- 只能调用一个构造函数
销毁对象
-
根据对象占有的资源决定自动/显式回收
-
- new 操作时系统分配的内存(系统自动回收)
-
- 其他资源(程序显式申请)
-
垃圾回收不回收程序显式申请的资源
public class Resource { int i; BufferedReader f; Resource() { i = 0;} void open() { f= new BufferedReader(new FileReader(new File(“a.txt”))); } String readLine() { return f.readline();} void close() { f.close(); } public static void main(String [ ]args) { Resource r = new Resource(); r.open(); //… r.close();//手动回收 } }
-
垃圾回收
-
- 对象的引用数,指指向该对象的引用的个数
-
- 引用数为0时自动回收
String s = new String(“hello”); String t = new String(“world”); s = t;
- 引用数为0时自动回收
-
- 内容为 ''hello'' 的对象可回收,但不会立即被回收
-
- 当 Java 虚拟机 (JVM) 发现内存不够时尝试进行回收
-
- 由 JVM 决定是否回收 , 何时回收 ( 并非实时进行 )
-
- 原因:垃圾回收将占用系统资源,影响用户程序;减少回收频率;
-
- System.gc() 调用:通知 JVM 可以进行垃圾回收
-
- 类的 finalize() 方法(避免使用)
JVM实现垃圾回收
算法1:引用计数
- 每个对象包含一个计数器,记录指向改对象的引用数
- 垃圾收集器检查所有对象,如果引用数为0则删除
- 缺点
-
- 循环引用
Dog newDog = new Dog(); Tail newTail = new Tail(); newDog.tail = newTail; newTail.dog = newDog;
在这里,newTail中拿着对newDog的引用,newDog中拿着对newTail的引用。如果newDog要被回收,前提是newTail被先回收,这样才能释放对newDog的引用。但是反回过来,newTail要被回收的前提是newDog要被先回收。当buildDog函数退出后,看起来垃圾回收管理似乎就始终无法回收这两个实际已经不再需要的对象。
- 循环引用
算法2:stop-and-copy
- 找出所有有效的对象(从栈上的引用出发)
- 每找到一个有效对象,将其拷贝到另外一块内存区域
- 修改所有引用的值
- 被垃圾搜集的程序将被停止
- 缺点
这种“复制式回收器”效率会降低,有两个原因,
首先,要有两个堆,然后在这两个堆之间倒腾,从而得维护比实际需要的多一倍的时间。
在java虚拟机对此问题的处理方式是,分配几块较大的内存,复制动作发生在这些大的内存块之间。
算法3:mark-and-sweep
这种体制同样也要先暂停程序才能进行,它的思想同样追随引用,从堆栈或静态存储区出发,遍历所有的引用,进而找出所有存活对象,找到一个存活对象就标记一个。这个过程不会发生回收任何对象,而是全部标记工作完成后,才会清理未被标记的对象。这个过程不会发生任何复制动作,所以剩下的堆空间是不连续的,垃圾回收器如果希望得到连续空间的话,要重新整理剩下的对象。
相对的,当程序产生垃圾较多时,效率较低。
算法4:二者结合
- 当较容易产生垃圾时: 使用 stop-and-copy
- 当不容易产生垃圾时: 使用 mark-and-sweep
- 判断情况
-
- 大多数对象的生存周期较短
-
- 每个对象记录生存周期
-
- 根据生存周期归类
-
- 生存周期较小的对象 : 使用 stop-and-copy
-
- 生存周期较大的对象 : 使用 mark-and-sweep
初始化的顺序
- 所有数据成员初始化在构造函数调用前完成
- 按照定义的顺序初始化
静态成员初始化
- 当第一个该类型的对象被创建时初始化
- 后续创建该类型的对象时不初始化
- 先初始化静态数据成员 , 后初始化对象数据成员
Lecture5
Java包
- 由多个类组成
- 多个类共享一个命名空间
创建包
- package语句
-
- 指定当前 java 文件中的类属于哪一个包
- 包的结构与文件目录结构一致
//.\com\runoob\test\Runoob //文件名:Runoob.java package com.runoob.test
访问控制
- 类 : 数据 + 方法
- 公开的数据和方法
● 提供服务
● 所有用户都可以使用
● 需要保持稳定 , 不经常变化 - 隐藏的数据和方法
● 细节的 , 辅助性的方法和数据
● 不向用户公开
● 易变
package access
- 一个类的成员被标识为 package access
-
- 同一个包中的类可以访问
-
- 其他包中的类不能访问
- 没有标识符
- 如果没有 package 语句 , java 默认当前目录中的 java文件属于同一个包
public
- 类的成员标识为 public,则所有用户都能访问该成员
private
- 类的成员标识为 private,则除了该类自身外的所有类都无法访问该成员
protected
- 开放继承类的权限
权限控制表
修饰词 | 本类 | 同一个包的类 | 继承类 | 其他类 |
---|---|---|---|---|
private | √ | × | × | × |
无(默认) | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
类的访问控制
- public class
-
- 每个 .java 文件包含一个 public class
-
- 该 class 的名字等于 .java 文件名
- package access class
-
- 每个 .java 文件中除去 public class 外 , 其他的 class为 package access
封装
- 访问控制的设计
public
- 提供服务,稳定
- 修改 public 成员将影响用户程序
private
-
提供辅助,易变
-
修改 private 成员是安全的
-
满足需求的情况下,接口尽可能简单
-
尽量使用 private 访问控制
Lecture6
类的复用
- 组合
- 继承
组合
- 将已有类的对象作为新类的数据成员
//MyType
class MyType {
public int i;
public double d;
public char c;
public void set(double x) { d = x;}
public double get() { return d; }
}
//MyCompType
public class MyCompType {
private MyType m = new MyType();
private String s;
public MyCompType(){
s = new String(“Hello”);
}
}
继承
- 子类有父类所有的方法和数据
class MyType {
public int i;
public double d;
public char c;
public void set(double x) { d = x;}
public double get() { return d; }
}
public class MySubType extends MyType{
public static void main(String []args){
MySubType ms = new MySubType();
ms.set(1.0);
System.out.println(ms.get());
System.out.println(ms.i);
}
}
- 子类可以定义新的方法和数据
- 子类可以更新父类的方法,称为重写 (overriding)
- 定义子类时,创建一个新的类,包含一个父类的对象作为数据成员
public class MySubType extends MyType{
/*
public MyType m;
*/
public string s;
public childMethods() {...}
}
- 子类的对象包含一个隐藏的父类对象,通过 super 引用父类的对象
- 当方法被重写时,可以通过 super 调用父类的方法
Lecture7
upcasting
- 不同类型的对象之间无法做赋值运算
- 子类类型的对象可以赋值给父类
- 子类是一种父类 “is-a”关系
- upcasting 例子
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
// ...
}
}
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute);
}
}
- 上面例子中,flute是Instrument子类的引用,可以当作Instrument类型的引用
- 需要父类对象的地方可以用子类对象带入
- 一种安全的类型转换
downcasting
- 父类对象的引用赋值给子类对象的引用
public class MySubType extends MyType {
public void set(double x) {
System.out.println("sub class");
d = x;
}
public static void main(String[] args) {
MySubType ms = new MySubType();
ms.set(1.0);
MyType m = new MyType();
//downcasting 无法通过编译
MySubType n = (MySubType) m;
n.set(1.0);
}
}
- 可以通过编译的情况:将子类对象的引用先赋值给父类类型的引用,然后将此变量强制类型转换为子类对象
public class MySubType extends MyType {
public void set(double x) {
System.out.println("sub class");
d = x;
}
public static void main(String[] args) {
MySubType ms = new MySubType();
ms.set(1.0);
MyType m = ms;
m.set(1.0);
//downcasting 可以通过编译
MySubType n = (MySubType) m;
n.set(1.0);
}
}
- 总结:父类的引用可以指向子类的对象
final关键字
- 基本意义为:无法改变
- 一旦被赋值就无法被修改
class MyType {
public int i;
public final double d = 1;
public char c;
public double get() { return d; }
public void set(double x) {d = x;}
public static void main(String []args){
MyType m = new MyType();
// m.d = 2.0; error
}
}
使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。
final关键字修饰方法,它表示该方法不能被覆盖(重写)。另外,类中所有的private方法都隐式地指定为是final的,由于无法在类外使用private方法,所以也就无法覆盖它。此时可以在子类中定义相同的方法名和参数,这种情况不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。可以对private方法添加final修饰符,但并没有添加任何额外意义。
final关键字修饰类,表示该类无法被继承。
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
-
编译期间为常量,而非引用
-
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
-
final 成员在定义时可以不给初值,但必须在构造函数中初始化
Lecture8
upcasting简化接口
class Instrument {
public void play(int note) {
System.out.println(“Instrument.play()” + n);
}
}
public class Wind extends Instrument {
public void play(int note) {
System.out.println(“Wind.play()” + n);
}
}
public class Stringed extends Instrument {
public void play(int note) {
System.out.println(“Stringed.play()” + n);
}
}
public class Brass extends Instrument {
public void play(int note) {
System.out.println(“Brass.play()” + n);
}
}
//Without upcasting
/*
public class Music {
public static void tune(Wind i) {
i.play();
}
public static void tune(Stringed i) {
i.play();
}
public static void tune(Brass i) {
i.play();
}
public static void main(String []args){
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute);
tune(violin);
tune(frenchHorn);
}
}*/
//With upcasting
/*
public class Music {
public static void tune(Wind i) {
i.play();
}
public static void tune(Stringed i) {
i.play();
}
public static void tune(Brass i) {
i.play();
}
public static void main(String []args){
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute);
tune(violin);
tune(frenchHorn);
}
}
*/
- 接口更加简洁
- tune方法能正确调用对应的重写 (override) 后的子类方法
多态
- 如上例,参数 Instrument i 可以代表不同的子类 , 并能正确调用它们的方法 ( 即有多种表现形态)
- 静态绑定(static binding):函数的位置在编译时确定
- 动态绑定(dynamic binding):函数的位置在运行时才能确定
upcasting + 多态带来的扩展性
- 增加新的接口不影响原有的只依赖于旧接口的代码
动态绑定
- Java中所有的方法都采用动态绑定,除了static和final
- 数据成员不使用动态绑定
- 构造函数中不能使用重写函数
Lecture9
抽象类
抽象方法
- 仅提供方法的名称,参数列表,和返回值
- 没有具体实现
- 使用abstract关键字
abstract class Instrument{
public abstract void play(int note);
}
- 无法直接创建抽象类的对象
- 抽象方法需要在子类中补足定义才有意义
- 若子类没有重写父类中的抽象方法,子类仍为抽象类
接口
- 所有方法都是抽象方法
- 只有方法的名称,参数和返回值
- 没有方法的实现
interface Instrument {
void play(int note) ;
String what();
}
-
没有代码重用,只保留upcasting和多态
-
所有实现该接口的类都具有接口提供的方法
-
任何使用该接口类型的方法 , 都可以使用他的任何一种实现
class Stringed implements Instrument {
public void play(int note) {
System.out.println(“Stringed.play()” + n);
}
public String what() {return “Stringed”;}
}
- 所有方法默认为public
- 所有数据默认为final static
实现多个接口
interface Plane {
void fly();
}
interface Boat {
void sail();
}
class Seaplane implements Plane, Boat {
public void fly(){
System.out.println(“Fly!”);
}
public void sail(){
System.out.println(“Sail!”);
}
}
-
多继承问题:一个类的父类只能有一个普通类/抽象类
-
一个类无法继承拥有不同函数(却有相同函数名)的不同接口
interface I1 {
void f();
}
interface I2 {
void f();
}
interface I3 {
void f(int i);
}
interface I4 {
int f();
}
class C1 implements I1, I2{
public void f() {}
}
class C2 implements I1, I3{
public void f() {}
public void f(int i) {}
}
/* compile error: return type incompatible
class C2 implements I1, I4{
public void f() {}
}
*/
扩展接口
interface A {
…
}
interface B extends A{
…
}
interface D {
…
}
interface D extends A, C{
…
}
接口适配器
class Person{
public void walk(){}
public void buyTicket(){}
public void takeFlight(){}
}
class PersonAdapter implements CanFly{
private Person p;
public PersonAdapter(Person p){
this.p = p;
}
public void fly(){
p.buyTicket();
p.takeFlight();
}
}
interface CanFly {
void fly();
}
class Adventure {
public static void travel(CanFly c) {
c.fly();
}
public static void main(String []args){
Bird b = new Bird();
Insect ins = new Insect();
travel(b); travel(ins);
Person p = new Person();
PersonAdapter pd = new PersonAdapter(p);
travel(pd);
}
}
- 在PersonAdapter中组合Person和接口方法,使得接口方法能够使用Person这一类型
工厂模式
- 定义一个Factory接口,通过这个接口,根据用户需求创建类
Lecture10
内部类
- 定义在一个类的内部,与组合不同
内部类与外部类的关系
-
内部类隐藏了一个指向外部类对象的引用
-
访问外部类对象的引用:OuterClassName.this
-
在外部类中创建引用:直接创建
-
在其他地方创建内部类的引用:OuterClassName.new
-
内部类通常实现某个接口/继承某个类
定义在某作用域中的内部类
- 作用域外不可见
- 也称为local inner class
匿名类
- 没有名字的内部类
- 必须继承某个类,或者某个接口
public class Parcel{
public Contents contents(){
return new Contents() {
// anonymous inner class definition
private int i = 11;
public int value() {return i;}
};
}
public static void main(String []args){
Parcel p = new Parcel();
Contents c = p.contents();
}
}
- 必须使用final的外部变量对成员进行初始化(可以不初始化,但初始化必须要final)
嵌套类
- 不包含指向外部类对象的引用
- 无法访问外部类的非静态成员
内部类的作用
-
多继承
-
可以通过多个内部类继承多个类/接口
-
同一个内部类可以有多个实例 , 每个实例有不同的状态
-
对同一接口 , 可以有不同的内部类实现
-
创建内部类对象可以按需创建
-
不必遵从 is-a 关系
Lecture11
容器
数组
int [ ] a = new int[]{1,2,3};
MyType [ ] b = new MyType[3];
MyType [ ] c = new MyType[3] {
new MyType(),
new MyType(),
new MyType()
};
- 长度不可变,无法添加删除元素
ArrayList
import java.util.*;
ArrayList<String> a = new ArrayList<String>();
// 插入 add(Object o)
a.add(“rat”); a.add(“cat”); a.add(“dog”); a.add(“dog”);
// 查询 contains (Object o)
System.out.println(a.contains(“cat”));
// 删除 remove(Object o) ( 若不在 List 中 , 返回 false, 否则返回 true)
a.remove(“dog”); a.remove(“dag”);
// 访问第 i 个元素 : get(int)
a.get(0);
// 对象的数量 : size()
a.size();
// 序号 indexOf
a.indexOf(“cat”);
// 子表 subList(int fromIndex, int toIndex)
List<String> sub = a.subList(2, 3);
// 是否为空 isEmpty()
System.out.println(a.isEmpty());
// 返回迭代器 iterator()
Iterator it = a.iterator();
// 返回 List 迭代器 listIterator()
ListIterator lit = a.listIterator();
// 转为数组
String [] aarray = a.toArray();
- 排序
public class RunoobTest {
public static void main(String[] args) {
ArrayList<String> sites = new ArrayList<String>();
sites.add("Taobao");
sites.add("Wiki");
sites.add("Runoob");
sites.add("Weibo");
sites.add("Google");
Collections.sort(sites); // 字母排序
for (String i : sites) {
System.out.println(i);
}
}
}
LinkedList
public class RunoobTest {
public static void main(String[] args) {
LinkedList<String> sites = new LinkedList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
// 使用 addFirst() 在头部添加元素
sites.addFirst("Wiki");
sites.addLast("Wiki");
System.out.println(sites);
}
}
//result: [Wiki, Google, Runoob, Taobao, Wiki]
其余容器可以查看
https://www.runoob.com/java/java-hashmap.html
Lecture12
异常处理
抛出异常
- 检查错误条件
- throw关键字
If (t == null) throw new NullPointerException();
处理异常
try {
// 可能会抛出异常的代码
}
catch (Type1Exception e){
// 处理类型为 "Type1Exception" 的异常
}
catch (Type2Exception e){
// 处理类型为 "Type2Exception" 的异常
}
catch (Type3Exception e){
// 处理类型为 "Type3Exception" 的异常
}
...
异常对象
- Exception的子类
- 含有toString(), printStackTrace()方法
- 通常不需要重写 Exception 类中的任何方法
class SimpleException extends Exception { }
public class InheritingExceptions {
public static void main(String[] args) {
try {
System.out.println("Throw SimpleException from f()");
throw new SimpleException();
} catch(SimpleException e) {
System.out.println("Caught it!");
System.out.println(e);
System.out.println(e.printStackTrace(System.out));
}
}
}
类方法的异常说明
- 标识该方法抛出什么样的异常
bar() throws Type1Exception, Type2Exception{
…
throw new Type1Exception ();
…
throw new Type2Exception ();
}
如果方法使用了 throws 关键字,则在该方法的调用处必须要处理相应的异常,否则编译不通过。
- 包含 throws 关键字 , 但函数本身并不抛出相应异常
-
- 用于 interface, abstract method
-
- 保证重写的方法必须考虑所列出的异常
- 不包含 throws 关键字,默认会抛出 RuntimeException 类型的异常
Java标准异常
- 都为 Exception 类的子类
- 通过名字知道种类
Lecture13
I/O
I/O流
- 数据的流向
-
- 外界数据进入程序
-
- 程序数据进入外界
- 数据的内容
-
- 字节
-
- 文件
-
- 字符串
-
- 对象
public static void fileOutput() throws IOException{
String str = "hello world!";
File file = new File("d:\\test2.txt"); // 创建文件
if(!file.exists()){
file.createNewFile(); // 如果文件不存在,则进行创建
}
FileOutputStream fOutput = new FileOutputStream(file);
BufferedOutputStream bOutput = new BufferedOutputStream(fOutput);
byte[] buffer = str.getBytes(); // 将字符串文本转换成字节数组
bOutput.write(buffer);
bOutput.close();
fOutput.close();
}
public static void fileInput() throws IOException{
File file = new File("d:\\test2.txt"); // 创建文件
FileInputStream finput = new FileInputStream(file);
BufferedInputStream bfinput = new BufferedInputStream(finput);
int temp = 0;
while((temp = bfinput.read())!= -1){ // 当 temp 为 -1 时,数据读取完毕
System.out.print((char)temp);
}
bfinput.close();
finput.close();
}
Path类型
- 创建path类型
Path p1 = Paths.get("C:/Document/tmp/Hello.java");
Path p2 = FileSystems.getDefault().getPath("C:/Document/tmp/Hello.java");
// Microsoft Windows syntax
Path path = Paths.get("C:\\home\\joe\\foo");
// Solaris syntax
// Path path = Paths.get("/home/joe/foo");
System.out.format("toString: %s%n", path.toString());
System.out.format("getFileName: %s%n", path.getFileName());
System.out.format("getName(0): %s%n", path.getName(0));
System.out.format("getNameCount: %d%n", path.getNameCount());
System.out.format("subpath(0,2): %s%n", path.subpath(0,2));
System.out.format("getParent: %s%n", path.getParent());
System.out.format("getRoot: %s%n", path.getRoot());
File类
参考https://www.runoob.com/java/java-files-io.html
牛啊
又发现了一位大佬,来加个友链嘛~~
https://www.jxtxzzw.com