面向对象基础
面向对象编程
面向对象基础
面向对象编程,是一种通过对象的方式,把现实世界映射到计算机模型的一种编程方法。
抽象的是类(class
)
具体的是实例(instance
)
class和instance
class
是一种对象模版,它定义了如何创建实例,因此,class
本身就是一种数据类型
instance
是对象实例,instance
是根据class
创建的实例,可以创建多个instance
,每个instance
类型相同,但各自属性可能不相同
定义class
一个class
可以包含多个字段(field
),字段用来描述一个类的特征.
创建实例
用new
操作符创建类的实例,创建了一个类型的实例,并通过变量a
指向它,可以通过这个变量来操作实例。访问实例变量可以用变量.字段.
方法
可以用private
修饰field
,拒绝外部访问。
需要使用方法(method
)来让外部代码可以间接修改field
一个类通过定义方法,就可以给外部代码暴露一些操作的接口,同时,内部自己保证逻辑一致性。
调用方法的语法是实例变量.方法名(参数);
定义方法
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
内部方法是可以调用private
方法
this变量
在方法内部,可以使用一个隐含的变量this
,它始终指向当前实例,通过this.field
就可以访问当前实例的字段
方法参数
方法可以包含0个或任意个参数。方法参数用于接收传递给方法的变量值。调用方法时,必须严格按照参数的定义一一传递.
可变参数
可变参数用类型...
定义,可变参数相当于数组类型
参数绑定
调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定。
基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方
构造方法
创建实例的时候,实际上是通过构造方法来初始化实例的。调用构造方法,必须用new
操作符。
默认构造方法
一个类没有定义构造方法,编译器会自动生成一个默认构造方法
如果自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来
创建对象实例的时候,按照如下顺序进行初始化:
先初始化字段
执行构造方法的代码进行初始化
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)
方法重载
有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法
继承
Java
使用extends
关键字来实现继承
子类自动获得了父类的所有字段,严禁定义与父类重名的字段
Java
只允许一个class
继承自一个类,因此,一个类有且仅有一个父类。只有Object
特殊,它没有父类。
protected
子类无法访问父类的private
字段或者private
方法
protected
关键字可以把字段和方法的访问权限控制在继承树内部,一个protected
字段和方法可以被其子类,以及子类的子类所访问
super
super
关键字表示父类(超类)
子类引用父类的字段时,可以用super.fieldName
在Java
中,任何class
的构造方法,第一行语句必须是调用父类的构造方法
如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super()
;
如果父类没有默认的构造方法,子类就必须显式调用super()
并给出参数以便让编译器定位到父类的一个合适的构造方法
子类不会继承任何父类的构造方法
阻止继承
向上转型
一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting
)
父类变量可以指向子类类型实例
向上转型实际上是把一个子类型安全地变为更加抽象的父类型
向下转型
一个父类类型强制转型为子类类型,就是向下转型(downcasting
)
不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
向下转型很可能会失败。失败的时候,Java
虚拟机会报ClassCastException
。
instanceof
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。
从Java 14
开始,判断instanceof
后,可以直接转型为指定变量,避免再次强制转型。
这里总结一下:
- 父类强制转换子类
Father f = new Father();
Son s = (Son)f;//出错 ClassCastException
父类实例转换成子类不行
- “假”父类转换子类
Father f = new Son();
Son s = (Son)f;
父类对象引用子类实例,再强制转换可以
- 子类强制转父类
Son s = new Son();
Father f = (Father)s;
父类变量可以指向子类实例
区分继承和组合
继承是is
关系,组合是has
关系。
多态
在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override
)。
子类覆写了父类的方法
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.run?
}
}
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
这里是执行 Student.run()
,这里就是Person
变量指向Student
实例,实际调用的是实例里的方法。
Java
的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。
多态
多态:针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。多态的特性:运行期才能动态决定调用的子类方法。
多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
覆写Object方法
所有的class
最终都继承自Object
,所以可以覆写Object
中的方法。
调用super
在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super
来调用。
final
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final
修饰的方法不能被Override
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final
用final
修饰的字段在初始化后不能被修改。
抽象类
把一个方法声明为abstract
,表示它是一个抽象方法,本身没有实现任何方法语句。
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法:
class Person {
public abstract void run();
}
Person
类也无法被实例化。必须把Person
类本身也声明为abstract
,才能正确编译它
抽象类
存在抽象方法,那么那个类就需要申明抽象类。
因为无法执行抽象方法,因此这个类也必须申明为抽象类
使用abstract
修饰的类就是抽象类。我们无法实例化一个抽象类
抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错
面向抽象编程
- 上层代码只定义规范
- 不需要子类就可以实现业务逻辑
- 具体的业务逻辑由不同的子类实现,调用者并不关心。
接口
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现
如果一个抽象类没有字段,所有方法全部都是抽象方法
可以把该抽象类改写为接口:interface
。
使用interface
可以声明一个接口,不允许有字段
当一个具体的class
去实现一个interface
时,需要使用implements
关键字
一个类只能继承自另一个类,不能从多个类继承。
一个类可以实现多个interface
接口继承
一个interface
可以继承自另一个interface
,使用extends
公共逻辑适合放在abstract class
中,具体逻辑放到各个子类,而接口层次代表抽象程度,使用的时候,实例化的对象永远只能是某个具体的子类,但总是通过接口去引用它,因为接口比抽象类更抽象,使用上转型
default方法
接口中,可以定义default
方法。实现类可以不必覆写default
方法
静态字段和静态方法
实例字段:在一个class
中定义的字段,每个实例都有独立的字段,各个实例的同名字段互不影响。
静态字段(static field
):静态字段只有一个共享“空间”,所有实例都会共享该字段。
静态字段并不属于实例,虽然实例可以访问静态字段,但是它们指向的其实都是类名 class
的静态字段,所有实例共享一个静态字段.推荐用类名来访问静态字段类名.静态字段
.
静态方法
用static
修饰的方法称为静态方法。调用静态方法则不需要实例变量,通过类名就可以调用.静态方法内部,无法访问this
变量,也无法访问实例字段,它只能访问静态字段。
接口的静态字段
interface
是可以有静态字段的,并且静态字段必须为final
类型
public static final
,这里可以省略。
包
Java
定义了一种名字空间,称之为包:package
.
类名(比如Person
)只是一个简写,真正的完整类名是包名.类名
在定义class
的时候,需要在第一行声明这个class
属于哪个包。
在Java
虚拟机执行的时候,JVM
只看完整类名,因此,只要包名不同,类就不同。
包没有父子关系。java.util
和java.util.zip
是不同的包,两者没有任何继承关系。
包作用域
位于同一个包的类,可以访问包作用域的字段和方法,不用public、protected、private
修饰的字段和方法就是包作用域
import
在一个class
中引用其他class
- 直接写出完整类名
- 用
import
语句,在写import
的时候,可以使用*,表示把这个包下面的所有class
都导入进来- 默认自动
import
当前package
的其他class
- 默认自动
import java.lang.*
- 默认自动
作用域
public
定义为public
的class
、interface
可以被其他任何类访问
private
定义为private
的field、method
无法被其他类访问
确切地说,private
访问权限被限定在class
的内部,而且与方法声明顺序无关
Java
支持嵌套类,如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问private
的权限
protected
protected
作用于继承关系。定义为protected
的字段和方法可以被子类访问,以及子类的子类
package
包作用域是指一个类允许访问同一个package
的没有public、private
修饰的class
,以及没有public、protected、private
修饰的字段和方法。
局部变量
局部变量作用域从变量声明处开始到对应的块结束。方法参数也是局部变量。
final
用final
修饰class
可以阻止被继承
用final
修饰method
可以阻止被子类覆写
用final
修饰field
可以阻止被重新赋值
内部类
Inner Class
如果一个类定义在另一个类的内部,这个类就是Inner Class
Inner Class
的实例不能单独存在,必须依附于一个Outer Class
的实例,可以修改Outer Class
的private
字段
Anonymous Class
还有一种定义Inner Class
的方法,它不需要在Outer Class
中明确地定义这个Class
,而是在方法内部,通过匿名类(Anonymous Class
)来定义。
在方法内部实例化了一个Runnable
, Runnable
本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable
接口的匿名类,并且通过new
实例化该匿名类,然后转型为Runnable
。在定义匿名类的时候就必须实例化它.除了接口外,匿名类也完全可以继承自普通类。
Static Nested Class
使用static
修饰,称为静态内部类(Static Nested Class
)它不再依附于Outer
的实例,而是一个完全独立的类,因此无法引用Outer.this
.但它可以访问Outer
的private
静态字段和静态方法。
classpath和jar
classpath
是JVM
用到的一个环境变量,它用来指示JVM
如何搜索class
Java
是编译型语言,源码文件是.java
,而编译后的.class
文件才是真正可以被JVM
执行的字节码
classpath
就是一组目录的集合,它设置的搜索路径与操作系统相关
在IDE
中运行Java
程序,IDE
自动传入的-cp
参数是当前工程的bin
目录和引入的jar
包
JVM根本不依赖classpath加载核心库
jar包
可以把package
组织的目录层级,以及各个目录下的所有文件(包括.class
文件和其他文件)都打成一个jar
文件
jar
包实际上就是一个zip
格式的压缩文件,而jar
包相当于目录
要执行一个jar
包的class
,就可以把jar
包放到classpath
中
创建jar包
制作了一个zip
文件,把后缀从.zip
改为.jar
Java
社区提供了大量的开源构建工具,例如Maven
,可以非常方便地创建jar
包。
模块
jar
文件就是class
文件的容器。从Java 9
开始引入的模块,主要是为了解决“依赖”这个问题.原有的Java
标准库已经由一个单一巨大的rt.jar
分拆成了几十个模块,这些模块以.jmod
扩展名标识,可以在$JAVA_HOME/jmods
目录下找到它们.所有的模块都直接或间接地依赖java.base
模块,只有java.base
模块不依赖任何模块,它可以被看作是“根模块”