Java 是面向对象的程序设计语言,支持面向对象的三大特征:封装、继承、多态。Java 提供 private、protected、public三种访问控制修饰符来实现良好的封装,提供了 extends 关键字让子类继承父类,通过继承关系实现复用时,子类对象可以直接赋给父类变量,即这个变量具有多态性。
子类继承父类就可以继承到父类的成员变量和方法,如果访问控制允许,子类实例可以直接调用父类里定义的方法。 继承是实现类复用的重要手段
类和对象是面向对象的核心
[修饰符] class 类名 {// 零个到多个构造器定义……// 零个到多个成员变量……// 零个到多个方法……}
- 修饰符不是必填项。支持
public、final、abstarct,或者不填 - 构造器是一个类创建对象的根本途径。如果一个类没有构造器,系统会为该类提供一个默认的无参构造器,但若一个类有构造器,系统将不再为该类提供构造器
- 一个类中各成员之间的定义顺序没有任何影响,各成员之间可以相互调用,但是
static修饰的成员不能访问没有static修饰的成员 成员变量(也称字段)一般指
field,它的语法格式:[修饰符] 类型 成员变量名 [= 默认值]如果没有命名冲突,可以省略
thisclass 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关系不应该使用继承,而是使用组合```javaclass 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 {@Overridepublic void sound() {System.out.println("Cat.sound");}}public class Dog extends Animal {@Overridepublic 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, okObject o1 = p; // upcasting, okObject o2 = s; // upcasting, ok
向下转型:与向上转型相反,将父类类型强制转型为子类类型。向下转型很可能会使用,Java 提供 instanceof 操作符号用于判断一个实例是否是某种类型
Person p = new Person();System.out.println(p instanceof Person); // trueSystem.out.println(p instanceof Student); // falseStudent s = new Student();System.out.println(s instanceof Person); // trueSystem.out.println(s instanceof Student); // trueStudent 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 {@Overridepublic void getData() {System.out.println("PrinterA.getData");}@Overridepublic 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();}}
