Java
是面向对象的程序设计语言,支持面向对象的三大特征:封装、继承、多态。Java
提供 private
、protected
、public
三种访问控制修饰符来实现良好的封装,提供了 extends
关键字让子类继承父类,通过继承关系实现复用时,子类对象可以直接赋给父类变量,即这个变量具有多态性。
子类继承父类就可以继承到父类的成员变量和方法,如果访问控制允许,子类实例可以直接调用父类里定义的方法。 继承是实现类复用的重要手段
类和对象是面向对象的核心
[修饰符] class 类名 {
// 零个到多个构造器定义……
// 零个到多个成员变量……
// 零个到多个方法……
}
- 修饰符不是必填项。支持
public
、final
、abstarct
,或者不填 - 构造器是一个类创建对象的根本途径。如果一个类没有构造器,系统会为该类提供一个默认的无参构造器,但若一个类有构造器,系统将不再为该类提供构造器
- 一个类中各成员之间的定义顺序没有任何影响,各成员之间可以相互调用,但是
static
修饰的成员不能访问没有static
修饰的成员 成员变量(也称字段)一般指
field
,它的语法格式:[修饰符] 类型 成员变量名 [= 默认值]
如果没有命名冲突,可以省略
this
class Person {
private String name;
public String getName() {
return name;
}
}
如果有局部变量和成员变量重名,那么局部变量优先级更高,访问成员变量时必须加上 this
class Person {
private String name;
public void setName(String name) {
this.name = name;
}
}
static 关键字
static
是一个特殊的关键字,它可用于修饰方法、成员变量等成员。static
修饰的成员表明它属于这个类本身,而不属于这个类的实例,通过把static
修饰的成员变量和方法称为静态变量、静态方法,并且静态成员不能直接访问非静态成员。Java
中属性JavaBean
指的是一组setter
方法和getter
方法。比如某个类有age
属性,则这个类具有setAge()
和getAge()
两个方法静态字段和静态方法
静态字段
class
中用定义的字段 field
,称为实例字段,其中用 static
修饰的字段,称为静态字段。实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都共享该字段,也就是静态字段并不属于实例,它属于 class
本身的字段
- 静态方法
用 static
修饰的方法称为静态方法。静态方法属于 class 而不属于实例,因此静态方法内部,无法访问 this
变量和实例字段,只能访问静态字段。一般静态方法多用于工具类中
方法参数
- 方法可以包含 0 个或任意个参数。调用方法时,必须严格按照参数定义一一传递。
- 方法也可以定义可变参数。写法是 类型… ,调用方法时可以传递多个相同类型的参数
- 方法参数传递方式只有值传递
- 基本类型参数传递,是调用方值的复制。双方各自的后续修改,互不影响
基本类型参数传递时,系统将调用方的变量值复制赋值给接收方,并为调用方和接收方分配两块不同的内存栈,双方各自在这块栈中保存局部变量。
- 引用类型参数传递,调用方的变量和接收方的变量指向的是同一个对象。双方任意一方修改这个对象都会影响双方
系统创建一个对象时,对象本身保存在内存堆中,指向该对象的引用变量保存在内存栈中。引用类型参数传递时,系统将调用方的变量值复制赋值给接收方,但是由于该变量值只是一个引用变量(指向),所以系统只是复制了“引用”,并没有复制对象本身,这就导致当前存在两个引用指向同一个对象本身,那么双方任意一方通过引用指向修改对象本身时,其他一方都会受到影响
内部类
定义在 class
内部中的 class
,称为内部类 Nested Class
。
class Outer {
class Inner {
// 定义了一个Inner Class
}
}
Inner
是一个内部类,Inner Class
的实例不能单独存在,必须依附于一个 Outer Class
的实例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.hello();
重载
构造器重载
同一个类中存在多个构造器,多个构造器的参数列表不同,即被称为构造器重载。通常情况下,会在一个构造方法内部调用另一个构造方法,便于代码复用。
方法重载
同一个类中方法名相同,但参数列表不同,这种情况称为方法重载。通常情况下,方法重载的返回值类型都是相同的;方法名相同,参数列表相同,但方法返回值类型不同的方法不能作为方法重载。
封装
封装是面向对象编程语言对客观世界的模拟,将对象的状态信息隐藏在对象内部不允许外部直接访问,将方法暴露对外,调用者只能通过预定义的方法来操作成员变量
包
Java
引入包 package
机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。包可以是多层结构,用 .
隔开(例如 java.util
)。包没有父子关系,**java.util**
和 **java.util.zip**
是不同的包,两者没有任何继承关系。
最佳实践
- 为了避免命名冲突,使用域名倒写来作为包名
- 约定
Java
文件对应的目录层次和包的层次一致访问修饰符
访问控制级别从小到大
private (当前类私有访问) > default (同一包中访问) > protected (子类可访问) > public (公开访问)
private | default | protected | public | |
---|---|---|---|---|
同一个类中 | ✔️ | ✔️ | ✔️ | ✔️ |
同一个包中 | ✔️ | ✔️ | ✔️ | |
子类中 | ✔️ | ✔️ | ||
全局范围内 | ✔️ |
一个
.java
文件只能包含一个public
类,但可以包含多个非public
类。如果有public
类,文件名必须和public
类的名字相同。
继承
继承是实现代码复用的重要手段,Java
使用 extends
关键字来实现继承。在 OOP
术语中,Person
类称为 父类(super),Student
类称为 子类。子类继承父类除构造方法外的所有功能。java.lang.Object
类是所有类的直接或间接父类,并且 Java
的继承具有单继承特点,即每个子类都只有一个直接父类。
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
public Student(String name,int age,int score) {
super(name,age);
this.score = score;
}
}
super
关键字
- 子类引用父类的字段时,可以使用
super.[fieldName]
语法 - 子类必须使用
super()
调用父类构造函数并赋值相对应参数
大部分时候,子类总是以父类为基础,在基础上额外添加其他字段和方法,但有时子类需要重写父类的方法,Override
注解Java
提供Override
注解来实现重写 ```java public class Person { protected void check() {
} }System.out.println("Person.Check");
public class Student { @Override protected void check() { super.check(); System.out.println(“Student.check”); } }
<a name="zaGrc"></a>
#### `final` 关键字
- 用 `final` 修饰 `class` 可以阻止被继承
- 用 `final` 修饰 `method` 可以阻止被子类重写
- 用 `final` 修饰 `field` 可以阻止被重新赋值,未设置默认值时可以在构造器中赋值
- 用 `final` 修饰局部变量可以阻止被重新赋值
<a name="Ai3pV"></a>
#### 组合
继承是 is 关系,组合是 has 关系。具有has关系不应该使用继承,而是使用组合
```java
class Book {
protected String name;
public String getName() {...}
public void setName(String name) {...}
}
class Student extends Person {
protected Book book;
protected int score;
}
多态
Java
引用类型分为编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋值给该变量的对象决定。如果编译时类型和运行时类型不一致,这种情况就称之为多态,即运行期才能动态决定调用的具体类型。
多态是另一个实现代码复用的重要手段,它允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。
public class Animal {
public void sound() {
System.out.println("Animal.Sound");
}
}
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat.sound");
}
}
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog.sound");
}
}
public class Main {
public static void main(String[] args) {
Animal[] animals = new Animal[] {
new Dog(),
new Cat()
};
for (Animal animal : animals) {
animal.sound();
}
// Dog.sound
// Cat.sound
}
}
向上转型:将子类类型安全的变为父类类型的赋值
Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok
向下转型:与向上转型相反,将父类类型强制转型为子类类型。向下转型很可能会使用,Java 提供 instanceof 操作符号用于判断一个实例是否是某种类型
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
抽象类和接口
抽象 abstract
和 接口 intertace
都是从多个子类中抽象出来的公共特征。抽象类的本质是定义接口规范约束子类,而接口就是比抽象还抽象的纯抽象接口,它甚至都没有字段。
通常情况下,定义 abstract
抽象类时,类名添加 Abstract
或 Base
前缀,定义 interface
接口时,类型添加 I
前缀
抽象类和接口两者比较
abstract class | interface | |
---|---|---|
继承 | 只能 extends 一个 class | 可以 implements 多个 interface |
字段 | 可以定义实例字段 | 不能定义实例字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义 default 方法 |
接口继承
一个 interface
可以继承自另一个 interface
。interface
继承自 interface
使用 extends
,它相当于扩展了接口的方法。例如:
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
设计继承关系
设计结构时,abstract
和 interface
通常会组合使用,公共逻辑放在 abstract class
中,接口规范放在 interface
中,接口的层级关系代表逻辑抽象程度。
典型案例:Java 集合类定义的一组接口、抽象类和具体子类
┌───────────────┐
│ Iterable │
└───────────────┘
▲ ┌───────────────────┐
│ │ Object │
┌───────────────┐ └───────────────────┘
│ Collection │ ▲
└───────────────┘ │
▲ ▲ ┌───────────────────┐
│ └──────────│AbstractCollection │
┌───────────────┐ └───────────────────┘
│ List │ ▲
└───────────────┘ │
▲ ┌───────────────────┐
└──────────│ AbstractList │
└───────────────────┘
▲ ▲
│ │
│ │
┌────────────┐ ┌────────────┐
│ ArrayList │ │ LinkedList │
└────────────┘ └────────────┘
List list = new ArrayList();
COllection collection = list;
Iterable it = collection;
面向接口编程
“面向接口”编程是软件架构设计核心理论,其根本目的是希望通过面向接口编程来降低程序的耦合
典型案例:简单工厂
public interface IOutput {
/**
* 读取内容
*/
void getData();
/**
* 打印内容
*/
void out();
}
public class PrinterA implements IOutput {
@Override
public void getData() {
System.out.println("PrinterA.getData");
}
@Override
public void out() {
System.out.println("PrinterA.print");
}
}
public class OutputFactory {
public static IOutput getOutput(){
return new PrinterA();
}
}
public class Computer {
private IOutput output;
public Computer(IOutput output){
this.output = output;
}
public void keyIn(String message){
output.getData();
}
public void print(){
output.out();
}
}
public class Main {
public static void main(String[] args) {
IOutput output = OutputFactory.getOutput();
var computer = new Computer(output);
computer.keyIn("hello world");
computer.print();
}
}