Java_oop

类和对象

面向对象编程(OOP)

面向过程–自己掌控整个过程
面向对象–关注的是完成这件事的对象,而无需知道过程。

  • 面向过程是流程化的,一步一步,上一步做完了,再做下一步。
  • 面向对象是模块化的,我做我的,你做你的,我需要你做的话,我就告诉你一声。我不需要知道你到底怎么做,只看功劳不看苦劳。

我们之前学习过了cpp,所以对面向对象、类等这些知识点还是相对熟悉的。

创建java对象时,需要使用new关键字。创建类对象也需要

  • 所有对象在创建的时候都会在堆内存中分配空间
  • 创建对象时需要一个main()方法作为入口,main()方法可以在当前类中,也可以在另一个类中。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
private String name;
private int age;
private int sex;

private void eat() {}
private void sleep() {}
private void dadoudou() {}

public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name);
System.out.println(person.age);
System.out.println(person.sex);
}
}
  • 与cpp不同的是,java作为一门纯粹的面向对象语言,它强制要求所有代码(包括程序的入口点)都必须封装在类中。

如果万物皆对象,都需要先创建对象实例才能调用方法,那第一个main方法是怎么被调用的呢?

  • 我们通过 static 关键字解决!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Restaurant { // 这是一个“饭店”(类)
// main方法是这个饭店的一个“特殊外卖窗口”,它不属于某个具体的厨师(实例)
// 这个窗口是饭店自带的,不需要先雇厨师就能使用。
public static void main(String[] args) {
System.out.println("餐厅开业了!");
// 现在,我们可以在内部创建“厨师”对象了
Chef chef1 = new Chef("王师傅");
chef1.cook("麻婆豆腐"); // 调用实例方法
}
}

class Chef {
String name;
public Chef(String name) {
this.name = name;
}
// 这是一个实例方法,属于具体的“厨师”对象
public void cook(String dish) {
System.out.println(name + "正在烹饪: " + dish);
}
}

static 意味着这个方法是属于类本身的,而不是属于由这个类创建出来的某个具体对象。

实际开发中,我们通常不在当前类中直接创建对象并使用它,而是放在使用对象的类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author 微信搜「沉默王二」,回复关键字 PDF
*/
public class PersonTest {
public static void main(String[] args) {
Person person = new Person();
}
}

class Person {
private String name;
private int age;
private int sex;

private void eat() {}
private void sleep() {}
private void dadoudou() {}
}

初始化类中变量的方法

方法一:直接通过引用变量来赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
private String name;
private int age;
private int sex;

public static void main(String[] args) {
Person person = new Person();
person.name = "沉默王二";
person.age = 18;
person.sex = 1;

System.out.println(person.name);
System.out.println(person.age);
System.out.println(person.sex);
}
}

方法二:通过方法初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author 沉默王二,一枚有趣的程序员
*/
public class Person {
private String name;
private int age;
private int sex;

public void initialize(String n, int a, int s) {
name = n;
age = a;
sex = s;
}

public static void main(String[] args) {
Person person = new Person();
person.initialize("沉默王二",18,1);

System.out.println(person.name);
System.out.println(person.age);
System.out.println(person.sex);
}
}

方法三:通过构造方法初始化
直接把要初始化变量的值当作参数传入,并赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author 沉默王二,一枚有趣的程序员
*/
public class Person {
private String name;
private int age;
private int sex;

public Person(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public static void main(String[] args) {
Person person = new Person("沉默王二", 18, 1);

System.out.println(person.name);
System.out.println(person.age);
System.out.println(person.sex);
}
}

在cpp中我们一般使用的就是方法3,java也是如此。

Object类

万物皆对象。万物指的是java中的所有类,这些类都是Object类的子类

对象比较

1
public native int hashCode();

用于返回对象的哈希码
按照约定,相等的对象必须具有相等的哈希码,如果重写了equals方法,就要重写hashCode方法。

1
2
3
public int hashCode(){
return Objects.hash(name,age);
}

这样子生成,相等的对象就会有相同的哈希码。

1
public boolean equals(Object obj)

用于比较两个对象的内存地址是否相等。

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

但我们一般会重写,修改成比较两个对象的值是否相等。

  • 根据类里的对象来设置,一般是一个个比较

对象拷贝

默认实现只做浅拷贝,且类必须实现Cloneable接口。
Object 本身没有实现 Cloneable 接口,所以在不重写 clone 方法的情况下直接直接调用该方法会发生 CloneNotSupportedException 异常。

对象转字符串

public String toString():返回对象的字符串表示。默认实现返回类名@哈希码的十六进制表示,但通常会被重写以返回更有意义的信息。

1
2
3
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

多线程调度

每个对象都可以调用Object 的 wait/notify 方法来实现等待/通知机制

反射

一开始不知道要初始化的类是什么,所以没办法用new关键字直接创造对象。

反射的缺点:

  • 破坏封装
  • 性能开销

反射的优点:

  • 开发通用框架:像 Spring,为了保持通用性,通过配置文件来加载不同的对象,调用不同的方法。
  • 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,而动态代理的底层技术就是反射。
  • 注解:注解本身只是起到一个标记符的作用,它需要利用反射机制,根据标记符去执行特定的行为。

public final native Class<?> getClass():用于获取对象的类信息,如类名。比如说:

1
2
3
4
5
6
7
public class GetClassDemo {
public static void main(String[] args) {
Person p = new Person();
Class<? extends Person> aClass = p.getClass();
System.out.println(aClass.getName());
}
}

垃圾回收

protected void finalize() throws Throwable:当垃圾回收器决定回收对象占用的内存时调用此方法。

package包

java中我们使用package来解决名字冲突

  • 一个类总是属于某个包,类名只是一个简写,真正的完整类名是 包名.类名

包可以是多层结构,用.隔开。例如:java.util

[!NOTE]
要特别注意:包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

没有定义包名的class,它使用的是默认包,非常容易引起名字冲突,因此,不推荐不写包名的做法。

包的作用域

包作用域是指:当一个类、接口、成员没有显式地使用任何访问修饰符时,它所拥有的访问权限。

位于同一个包的类,可以访问包作用域的字段和方法。
不用publicprotectedprivate修饰的字段和方法就是包作用域。

把一个类定义在包下:

1
2
3
4
5
6
7
8
9
package hello;
public class Person { // 包作用域: void hello() { System.out.println("Hello!"); } }

public class Main {
public static void main(String[] args) {
Person p = new Person();
p.hello(); // 可以调用,因为Main和Person在同一个包
}
}

这样子就将Person和Main都定义在hello下了。
main可以直接访问person类,它们位于同一包作用域下。

导入包:

直接写出完整类名

1
2
3
4
5
6
7
8
// Person.java
package ming;

public class Person {
public void run() {
mr.jun.Arrays arrays = new mr.jun.Arrays();
}
}

很麻烦

使用 import 语句

导入类后写简单类名。

1
2
3
4
5
6
7
8
9
10
11
// Person.java
package ming;

// 导入完整类名:
import mr.jun.Arrays;

public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}

import时可以使用通用标识符,直接将这个包下的所有class都导入进来,但是不包含子包的class

1
2
3
4
5
6
7
8
9
10
11
// Person.java
package ming;

// 导入mr.jun包的所有class:
import mr.jun.*;

public class Person {
public void run() {
Arrays arrays = new Arrays();
}
}

但是不推荐这种写法,导入多个包后,很难看出类属于哪个包

还有一种import static的语法,它可以导入一个类的静态字段和静态方法:

1
2
3
4
5
6
7
8
9
10
11
package main;

// 导入System类的所有静态字段和静态方法:
import static java.lang.System.*;

public class Main {
public static void main(String[] args) {
// 相当于调用System.out.println(…)
out.println("Hello, world!");
}
}

没怎么见过这个方法

在代码中,当编译器遇到一个class名称时:

  • 如果是完整类名,就直接根据完整类名查找这个class
  • 如果是简单类名,按下面的顺序依次查找:
    • 查找当前package是否存在这个class
    • 查找import的包是否包含这个class
    • 查找java.lang包是否包含这个class

这里我们知道,在编写class时,编译器会自动帮我们import:

  • import当前package的其他class
  • import java.lang.*

如果有两个class名称相同,例如,mr.jun.Arraysjava.util.Arrays,那么只能import其中一个,另一个必须写完整类名。

变量

Java 变量就好像一个容器,可以保存程序在运行过程中的值,它在声明的时候会定义对应的数据类型(Java 分为两种数据类型:基本数据类型引用数据类型
变量按照作用域的范围又可分为三种类型:局部变量,成员变量和静态变量

局部变量:

  • 在栈上分配
  • 没有默认值,被声明后必须经过初始化才可以使用
  • 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,将会被销毁。

声明成员变量时的注意事项:

  • 成员变量声明在一个类中,但在方法、构造方法和语句块之外。
  • 当一个对象被实例化之后,每个成员变量的值就跟着确定。
  • 成员变量在对象创建的时候创建,在对象被销毁的时候销毁。
  • 成员变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息。
  • 成员变量可以声明在使用前或者使用后。
  • 访问修饰符可以修饰成员变量。
  • 成员变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把成员变量设为私有。通过使用访问修饰符可以使成员变量对子类可见;成员变量具有默认值。数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。变量的值可以在声明时指定,也可以在构造方法中指定。

静态变量:

  • 静态变量在类中以 static 关键字声明,但必须在方法构造方法和语句块之外。
  • 无论一个类创建了多少个对象,类只拥有静态变量的一份拷贝
  • 静态变量除了被声明为常量外很少使用。
  • 静态变量储存在静态存储区
  • 静态变量在程序开始时创建,在程序结束时销毁。
  • 与成员变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。
  • 静态变量的默认值和实例变量相似。
  • 静态变量还可以在静态语句块中初始化。

常量:

  • 代表常数,便于修改(例如:圆周率的值,final double PI = 3.14
  • 增强程序的可读性(例如:常量 UP、DOWN 用来代表上和下,final int UP = 0

java要求常量名必须大写

方法

只有方法被调用时,它才会执行。Java 中最有名的方法当属 main() 方法,这是程序的入口。

如何声明方法

  • 方法的声明反映了方法的可见性(public、private、protected、default)、返回类型(int、void…)、方法名和参数

    访问权限:
  • public:该方法可以被所有类访问。
  • private:该方法只能在定义它的类中访问。
  • protected:该方法可以被同一个包中的类,或者不同包中的子类访问。
  • default:如果一个方法没有使用任何访问权限修饰符,那么它是 package-private 的,意味着该方法只能被同一个包中的类可见。

方法的种类

  • 标准类库方法
  • 用户自定义方法

预先定义方法

Java 提供了大量预先定义好的方法供我们调用,也称为标准类库方法,或者内置方法。

用户自定义方法

当一个方法被 static 关键字修饰时,它就是一个静态方法。换句话说,静态方法是属于类的,不属于类实例的(不需要通过 new 关键字创建对象来调用,直接通过类名就可以调用)

实例方法

  • 实例方法:没有static关键字修饰的,但是在类中有声明的方法。
  • 调用这种方法之前,必须创建类的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author 微信搜「沉默王二」,回复关键字 PDF
*/
public class InstanceMethodExample {
public static void main(String[] args) {
InstanceMethodExample instanceMethodExample = new InstanceMethodExample();
System.out.println(instanceMethodExample.add(1, 2));
}

public int add(int a, int b) {
return a + b;
}
}

实例方法有两种特殊类型:

  • getter 方法
  • setter 方法

getter 方法用来获取私有变量(private 修饰的字段)的值,setter 方法用来设置私有变量的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* @author 沉默王二,一枚有趣的程序员
*/
public class Person {
private String name;
private int age;
private int sex;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getSex() {
return sex;
}

public void setSex(int sex) {
this.sex = sex;
}
}

getter 方法以 get 开头,setter 方法以 set 开头。

静态方法

有 static 关键字修饰的方法就叫做静态方法。
StaticMethodExample 类中,mian 和 add 方法都是静态方法,不同的是,main 方法是程序的入口。当我们调用静态方法的时候,就不需要 new 出来类的对象,就可以直接调用静态方法了,一些工具类的方法都是静态方法,比如说 hutool 工具类库,里面有大量的静态方法可以直接调用。

抽象方法

没有方法体的方法被称为抽象方法,它总是在抽象类中声明。这意味着如果类有抽象方法的话,这个类就必须是抽象的。可以使用 abstract 关键字创建抽象方法和抽象类。

1
2
3
4
5
6
/**
* @author 微信搜「沉默王二」,回复关键字 PDF
*/
abstract class AbstractDemo {
abstract void display();
}

一个类继承抽象类之后,必须重写抽象方法

可变参数

允许方法接受零个或多个指定类型的参数。它使用省略号(...)表示,实际上是一个语法糖,底层仍然是数组。

  • 尽量不要使用可变参数,如果要用的话,可变参数需要在列表最后一位
  • 当使用可变参数的时候,实际上是先创建了一个数组,该数组的大小就是可变参数的个数,然后将参数放入数组当中,再将数组传递给被调用的方法

native本地方法

JNI:Java Native Interface

一般情况下,我们完全可以使用 Java 语言编写程序,但某些情况下,Java 可能满足不了需求,或者不能更好的满足需求,比如:

  • ①、标准的 Java 类库不支持。
  • ②、我们已经用另一种语言,比如说 C/C++ 编写了一个类库,如何用 Java 代码调用呢?
  • ③、某些运行次数特别多的方法,为了加快性能,需要用更接近硬件的语言(比如汇编)编写。

JNI 的缺点:

  • ①、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
  • ②、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了 Java 和 C/C++ 之间的耦合性。
    手把手教你用 C语言实现 Java native 本地方法 | 二哥的Java进阶之路

native 用来修饰方法,用 native 声明的方法表示该方法的实现在外部定义,可以用任何语言去实现它,比如说 C/C++。 简单地讲,一个 native Method 就是一个 Java 调用非 Java 代码的接口。

native 语法:

  • ①、修饰方法的位置必须在返回类型之前,和其余的方法控制符前后关系不受限制。
  • ②、不能用 abstract 修饰,也没有方法体,也没有左右大括号。
  • ③、返回值可以是任意类型

“三妹,你学的不错嘛。”我对三妹的学习能力感到非常的欣慰,“我们在日常编程中看到 native 修饰的方法,只需要知道这个方法的作用是什么,至于别的就不用管了,操作系统会给我们实现,初学的时候也不需要太过深入。”

构造方法

Java 有两种类型的构造方法:无参构造方法和有参构造方法

创建构造方法的规则

  • 构造方法的名字必须和类名一样;
  • 构造方法没有返回类型,包括 void;
  • 构造方法不能是抽象的(abstract)、静态的(static)、最终的(final)、同步的(synchronized)。

构造方法不能被子类继承,所以用final和abstract关键字修饰没有也意义
构造方法用于初始化一个对象,所以用static关键字修饰没有意义
多个线程不会同时创建内存地址相同的同一个对象,所以用 synchronized 关键字修饰没有必要。

1
2
3
4
5
6
class class_name {
public class_name(){} // 默认无参构造方法
public ciass_name([paramList]){} // 定义有参数列表的构造方法

// 类主体
}

如果写成了 void Demo() ,这并不是构造方法,而是会看作普通方法

默认构造方法

如果一个构造方法中没有任何参数,那么它就是一个默认构造方法,也称为无参构造方法。
无参构造方法是可以缺省的,我们开发者并不需要显式的声明无参构造方法

有参构造方法

有参构造方法可以为不同的对象提供不同的值。当然,也可以提供相同的值。

重载构造方法

  • 可以用于应对不同的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author 沉默王二,一枚有趣的程序员
*/
public class OverloadingConstrutorPerson {
private String name;
private int age;
private int sex;

public OverloadingConstrutorPerson(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

public OverloadingConstrutorPerson(String name, int age) {
this.name = name;
this.age = age;
}

public void out() {
System.out.println("姓名 " + name + " 年龄 " + age + " 性别 " + sex);
}

public static void main(String[] args) {
OverloadingConstrutorPerson p1 = new OverloadingConstrutorPerson("沉默王二",18, 1);
p1.out();

OverloadingConstrutorPerson p2 = new OverloadingConstrutorPerson("沉默王三",16);
p2.out();
}
}

构造方法主要是用于初始化对象的字段,并没有返回类型
构造方法的调用是隐式的,通过编译器完成

复制对象

复制一个对象可以通过下面三种方式完成:

  • 通过构造方法
  • 通过对象的值
  • 通过 Object 类的 clone() 方法

通过构造方法

1
2
3
4
5
6
7
8
9
10
public CopyConstrutorPerson(CopyConstrutorPerson person) { 
this.name = person.name; this.age = person.age;
}
public static void main(String[] args) {
CopyConstrutorPerson p1 = new CopyConstrutorPerson("沉默王二",18);
p1.out();

CopyConstrutorPerson p2 = new CopyConstrutorPerson(p1);
p2.out();
}

这里直接将对象p1作为参数传递给构造函数。(我们在前面重载了这个构造方法,使得可以通过这个方法来实现。

通过对象的值

很粗暴,直接一个个赋值

通过Object类的 clone() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @author 沉默王二,一枚有趣的程序员
*/
public class ClonePerson implements Cloneable {
private String name;
private int age;

public ClonePerson(String name, int age) {
this.name = name;
this.age = age;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

public void out() {
System.out.println("姓名 " + name + " 年龄 " + age);
}

public static void main(String[] args) throws CloneNotSupportedException {
ClonePerson p1 = new ClonePerson("沉默王二",18);
p1.out();

ClonePerson p2 = (ClonePerson) p1.clone();
p2.out();
}
}

通过 clone() 方法复制对象的时候,ClonePerson 必须先实现 Cloneable 接口的 clone() 方法,然后再调用 clone() 方法(ClonePerson p2 = (ClonePerson) p1.clone()

  • 但这里应该是浅拷贝

访问权限修饰符

在 Java 中,提供了四种访问权限控制:

  • 默认访问权限(包访问权限)
  • public
  • private
  • protected
    和cpp几乎一模一样

代码初始化块

代码初始化块用于初始化一些成员变量

抽象类

定义抽象类

定义的时候需要用到关键字 abstract

1
2
abstract class AbstractPlayer {
}

抽象类的特征

抽象类不可以实例化(即不能new)
但可以有子类,子类通过 extends 关键字来继承抽象类。

1
2
public class Son extends abstractFather{
}

如果一个类定义了一个或多个抽象方法,那这个类必须是抽象类。也就是说,只要类中定义了抽象方法,就一定是抽象类。

  • 普通类不可以定义抽象方法
  • 抽象类中可以定义抽象方法也可以定义普通方法。抽象方法没有方法体。

抽象类派生的子类必须实现父类中定义的抽象方法。(除非子类也是抽象类)

抽象类的应用场景

第一种场景:代码复用

  • 共享通用代码,避免重复代码。
    比如抽象类中定义了普通方法(有方法体),例如sleep()方法中打印 是个人都要睡觉。这样子子类可以直接继承复用。

第二种场景:子类实现API

抽象类中给出动物叫的API,子类继承时填写方法体。
比如之前我们在cpp学习过程中写过的动物叫,小狗就是汪汪叫,小猫就是喵喵叫等。

接口

  • java中可以通过两种形式来达到抽象的目的,一个是抽象类,一个是接口

定义接口

接口通过 interface 关键字来定义,它可以包含一些常量和方法。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Electronic {
// 常量
String LED = "LED";

// 抽象方法
int getElectricityUse();

// 静态方法
static boolean isEnergyEfficient(String electtronicType) {
return electtronicType.equals(LED);
}

// 默认方法
default void printDescription() {
System.out.println("电子");
}
}

其反编译后的字节码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Electronic
{

public abstract int getElectricityUse();

public static boolean isEnergyEfficient(String electtronicType)
{
return electtronicType.equals("LED");
}

public void printDescription()
{
System.out.println("\u7535\u5B50");
}

public static final String LED = "LED";
}

可以看到接口中的所有变量或方法都会自动加上 public 关键字

  • 接口中定义的变量会在编译的时候自动加上 public static final 修饰符。也就是说变量在接口里就是一个常量。
    Every field declaration in the body of an interface is implicitly public, static, and final.

  • 没有使用 private default或者 static 关键字修饰的方法是隐式抽象的。在编译的时候会自动加上 public abstract 修饰符。

  • java8开始,接口中允许有静态方法

  • default方法就是接口中定义的、有具体实现的方法。它允许接口在不破坏现有实现的情况下,添加新的方法。
    允许在接口中定义默认方法的理由很充分,因为一个接口可能有多个实现类,这些类就必须实现接口中定义的抽象类,否则编译器就会报错。假如我们需要在所有的实现类中追加某个具体的方法,在没有 default 方法的帮助下,我们就必须挨个对实现类进行修改。

一些特性

  • 接口不允许直接实例化。需要定义一个类去实现接口。
    例子:
1
2
3
4
5
6
7
8
9
10
11
public class Computer implements Electronic {

public static void main(String[] args) {
new Computer();
}

@Override
public int getElectricityUse() {
return 0;
}
}

这里使用了Computer类来实现Electronic接口
再进行实例化:

1
Electronic e=new Computer();
  • 接口可以为空,既可以不定义变量,也可以不定义方法。
  • 不要在定义接口的时候使用final关键字。因为接口就是为了让子类实现的,而 final 阻止了这种行为。
  • 接口的抽象方法不能是private,protected或者final
  • 接口的变量是隐式的public static final,值无法变

接口的作用

  • 使某些类具有我们想要的功能。实现了 Cloneable 接口的类具有拷贝的功能,实现了 Comparable 或者 Comparator 的类具有比较功能。

    • Cloneable 和 Serializable 一样,都属于标记型接口,它们内部都是空的。
  • Java 原则上只支持单一继承,但通过接口可以实现多重继承的目的。

  • 实现多态

    • 1、要有继承关系,比如说 Circle 和 Square 都实现了 Shape 接口。
    • 2、子类要重写父类的方法,Circle 和 Square 都重写了 name() 方法。
    • 3、父类引用指向子类对象,circleShape 和 squareShape 的类型都为 Shape,但前者指向的是 Circle 对象,后者指向的是 Square 对象。

内部类

  • 在类里定义类
  • 成员内部类、局部内部类、匿名内部类和静态内部类

成员内部类:在类里嵌套一个类。

1
2
3
4
5
6
7
class Wanger {
int age = 18;

class Wangxiaoer {
int age = 81;
}
}

成员内部类可以访问外部类的所有成员属性。
外部类要访问内部类需要先创建一个成员内部类对象,再通过对象来访问。
如果要在静态方法里访问成员内部类,需要先创建外部类,再创建外部类的成员内部类对象,进而访问成员内部类的成员。

局部内部类:在方法或者作用域里定义的类

局部内部类的生命周期仅限于作用域内。

1
2
3
4
5
6
7
8
public class Wangsan {
public Wangsan print() {
class Wangxiaosan extends Wangsan{
private int age = 18;
}
return new Wangxiaosan();
}
}

匿名内部类

  • 在创建对象的同时定义类体,没有显式的类名。通常用于实现接口或继承类
1
2
3
4
5
6
7
8
9
10
11
public class ThreadDemo {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
t.start();
}
}

这里:

1
2
3
4
5
6
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
  • 不能有构造器也不能是抽象类。
  • 访问外部变量需要final

静态内部类

  • 静态内部类:用static关键词修饰的定义在类里的类。
    不允许访问外部类中非static的变量和方法。

使用内部类还能够为我们带来如下特性:

  • 1、内部类可以使用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
  • 2、在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
  • 3、创建内部类对象的时刻并不依赖于外部类对象的创建。
  • 4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
  • 5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。

封装继承多态

封装和cpp几乎一模一样

继承

java的继承用到的关键字是 extends

  • java中的继承是单一继承,一个子类只能拥有一个父类,所以extends只能继承一个类。
1
class sonClass extends fatherClass{}

子类继承父类后,就拥有父类的非私有属性和方法

implements 关键字:可以变相使java拥有多继承的特性,使用范围为类实现接口的情况,一个类可以实现多个接口

1
2
3
class 类名 implements 接口1, 接口2, ..., 接口n {
// 必须实现所有接口的抽象方法
}

(接口中的方法不能具体定义,只能声明,所以继承接口必须重写接口内的方法)
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 定义多个接口
interface Swimmable {
void swim();
}

interface Flyable {
void fly();
}

interface Runnable {
void run();
}

// 一个类实现多个接口
class SuperDuck implements Swimmable, Flyable, Runnable {
private String name;

public SuperDuck(String name) {
this.name = name;
}

@Override
public void swim() {
System.out.println(name + "正在游泳");
}

@Override
public void fly() {
System.out.println(name + "正在飞翔");
}

@Override
public void run() {
System.out.println(name + "正在奔跑");
}

// 自己的特有方法
public void showAbilities() {
swim();
fly();
run();
}
}

// 使用
public class Test {
public static void main(String[] args) {
SuperDuck duck = new SuperDuck("超级鸭");
duck.showAbilities();

// 也可以按接口类型使用
Swimmable swimmer = duck;
Flyable flyer = duck;
Runnable runner = duck;

swimmer.swim();
flyer.fly();
runner.run();
}
}

这里就相当于继承了多个类

this 和 super

this 表示当前对象,是指向自己的引用。

1
2
3
4
this.属性 // 调用成员变量,要区别成员变量和局部变量
this.() // 调用本类的某个方法
this() // 表示调用本类构造方法(无参)
this(参数) // 表示调用本类有参构造方法

super 表示父类对象,是指向父类的引用。

1
2
3
super.属性 // 表示父类对象中的成员变量
super.方法() // 表示父类对象中定义的方法
super() // 表示调用父类构造方法

子类的构造过程必须调用其父类的构造方法: Java 虚拟机构造子类对象前会先构造父类对象,父类对象构造完成之后再来构造子类特有的属性,这被称为内存叠加。

  • 如果子类的构造方法中没有显示地调用父类构造方法,则系统默认调用父类无参数的构造方法。

方法重写和方法重载

方法重写就是把方法重新实现一次
方法重载是方法名相同,参数不一致。调用时会根据传递的参数进行选择。

向上转型

通过子类对象(小范围)实例化父类对象(大范围),也就是将子类对象赋值给父类类型的引用变量。

  • 自动转换

    父类引用变量指向子类对象后,只能使用父类已声明的方法,如果方法被重写,就会执行子类的方法。

向下转型

通过父类对象实例化子类对象(将父类对象赋值给子类对象)

  • 需要手动强制转换

在 Java 继承中,父子类初始化先后顺序为:

  1. 父类中静态成员变量和静态代码块
  2. 子类中静态成员变量和静态代码块
  3. 父类中普通成员变量和代码块,父类的构造方法
  4. 子类中普通成员变量和代码块,子类的构造方法

多态

Java 的多态是指在面向对象编程中,同一个类的对象在不同情况下表现出来的不同行为和状态。

  • 子类可以继承父类的字段和方法,子类对象可以直接使用父类中的方法和字段(私有的不行)。
  • 子类可以重写从父类继承来的方法,使得子类对象调用这个方法时表现出不同的行为。
  • 可以将子类对象赋给父类类型的引用,这样就可以通过父类类型的引用调用子类中重写的方法,实现多态。

多态和重载的区别

多态是动态多态,发生在运行时。通过继承和方法重写的方式实现。
重载是静态多态,发生在编译时。是同一类中的同名方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 父类
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}

// 子类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪!");
}
}

class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵喵!");
}
}

public class PolymorphismExample {
public static void main(String[] args) {
// 多态:编译时类型为Animal,运行时类型为具体子类
Animal animal1 = new Dog(); // 向上转型
Animal animal2 = new Cat(); // 向上转型

animal1.makeSound(); // 输出:汪汪汪!(运行时决定)
animal2.makeSound(); // 输出:喵喵喵!(运行时决定)

// 实际运行的方法由对象真实类型决定
demonstratePolymorphism(new Dog()); // 汪汪汪!
demonstratePolymorphism(new Cat()); // 喵喵喵!
}

// 多态方法:可以处理任何Animal子类
public static void demonstratePolymorphism(Animal animal) {
animal.makeSound(); // 动态绑定,运行时确定具体实现
}
}

多态是通过向上转型实现的,让不同类型的对象可以对同一方法调用做出不同的响应

static关键字

  • 方便在没有创建对象的情况下进行调用。属于类的方法,而不是对象的方法。
  • 通过 类名.方法名 来调用

静态变量:被整个类共用的变量。
如果是普通变量,是每个对象都有一个变量。

静态方法有以下这些特征。

  • 静态方法属于这个类而不是这个类的对象;
  • 调用静态方法的时候不需要创建这个类的对象;
  • 静态方法可以访问静态变量。

instanceof 关键字

用于判断对象是否符合指定的类型。
我们使用 instanceof 比较的目的,也就是希望如果结果为 true 的时候能进行类型转换。

不可变类

1)确保类是 final 的,不允许被其他类继承*。
2)确保所有的成员变量(字段)是 final 的,这样的话,它们就只能在构造方法中初始化值,并且不会在随后被修改。
3)不要提供任何 setter 方法
4)如果要修改类的状态,必须返回一个新的对象

Java注解

注解提供了一系列数据用来装饰程序代码(类、方法、字段等),但是注解并不是所装饰代码的一部分,它对代码的运行效果没有直接影响,由编译器决定该执行哪些操作。

注解的生命周期

  • SOURCE:在源文件中有效,被编译器丢弃。
  • CLASS:在编译器生成字节码文件中有效,在运行时会被处理类文件的jvm丢弃
  • RUNTIME:在运行时有效。允许程序通过反射的方式访问注解,并根据注解的定义执行相应的代码。

截止到 Java 9,注解的类型一共有 11 种,定义在 ElementType 枚举中。
1)TYPE:用于类、接口、注解、枚举
2)FIELD:用于字段(类的成员变量),或者枚举常量
3)METHOD:用于方法
4)PARAMETER:用于普通方法或者构造方法的参数
5)CONSTRUCTOR:用于构造方法
6)LOCAL_VARIABLE:用于变量
7)ANNOTATION_TYPE:用于注解
8)PACKAGE:用于包
9)TYPE_PARAMETER:用于泛型参数
10)TYPE_USE:用于声明语句、泛型或者强制转换语句中的类型
11)MODULE:用于模块


Java_oop
https://pqcu77.github.io/2025/11/10/Java-oop/
作者
linqt
发布于
2025年11月10日
许可协议