Java —— 面向对象基础
1. 面向对象
⟴ 面向过程(以方法为单位解决问题) ⟴ 面向对象(以对象为单位解决问题) ➤ 在Java中,一切皆对象,程序由对象之间的交互来实现功能 面向对象编程(OOP):一种编程思维方法,将问题抽象化为对象,并通过定义对象的属性(成员变量)和行为(方法)来描述问题 面向对象编程(OOP)的五个核心: 类,对象,封装,继承,多态 ▶ 类(Class)类是定义对象的模板或蓝图(描述对象的属性和行为),类型/类别/模板/,表示一类个体 ▶ 对象(Object)对象(具体的、有状态和行为的实体)是类的实例 OOP的三大特征 ▶ 封装(Encapsulation)封装是一种将数据和操作数据的方法捆绑在一起的机制,隐藏对象内部的实现细节,并通过公共接口提供访问数据的方式(封装提供了信息隐藏、保护数据完整性和实现代码复用的好处) ▶ 继承(Inheritance)继承是一种机制,允许一个类(子类)派生自另一个类(父类)的属性和方法。子类可以继承父类的特性,并可以添加自己的特定功能(继承实现了代码的重用性和层次结构的组织) ▶ 多态(Polymorphism)多态是指同一名字的操作可以在不同的对象上有不同的行为,允许使用统一的接口处理不同类型的对象(多态性增强了灵活性、可扩展性和可维护性) |
2. 类和对象
类的定义
成员变量:对象的属性(特征/数据)
成员方法:对象的行为(动作/功能)
创建类:
class 类名 {
类体
}
class 类名() {
// 成员变量
变量类型 变量名;
......
// 成员方法
[修饰词] 返回类型 方法名([参数列表]) {
方法体;
......
}
......
}
创建对象
将类创建完成后,使用new关键字创建对象(也称类的实例化)
为了能够对实例化对象进行访问控制,需要使用一个特殊的变量——引用
java数据类型:基本数据类型和引用数据类型(类,接口,数组)
访问成员变量及成员方法:
通过引用去访问对象的成员变量和成员方法
一般引用:引用.成员变量名
引用并赋值:引用.成员变量 = 新值
数据类型 应用类型变量(引用) 指向 对象
Random rand = new Random;
- 创建简单学生类
package oopbasic;
/** 创建学生类 */
public class Student {
// 成员变量
String name;
int age;
String classNum;
String id;
// 成员方法
void study(){
System.out.println("学习java");
}
// 查看学生信息
void show() {
System.out.println("姓名:" + name + " 年龄:" + age + " 班级:" + classNum + " 学号:" + id);
}
// 包含参数的方法
void play(String anotherName) {
System.out.println(name + "和" + anotherName + "正在打球!");
}
}
- 创建测试类
package oopbasic;
public class Test {
public static void main(String[] args) {
// 类的实例化
Student student = new Student();
// 访问成员变量
student.name = "张三";
student.age = 20;
student.classNum = "202301";
student.id = "001";
// 方法调用
student.study();
student.show();
student.play("李四");
}
}
3. 构造方法
构造方法(Constructor):一种特殊的方法,用于创建对象并初始化其实例变量 ▶ 作用:给成员变量赋初始值 ▶ 语法:名称必须与类名完全相同,并且没有返回类型(包括void) ▶ 调用:创建类的新对象时,构造方法会被自动调用 ▶ 特点:编译器默认提供一个无参构造方法,若自己写了构造方法,则不再默认提供(需要手动添加无参构造方法) |
this关键字:
this关键字的三种形式
访问变量:this.成员变量名
调用方法:this.方法名
调用构造方法:this()
this关键字只能用在方法中,方法中访问成员变量之前默认都有this
this指代当前对象,哪个对象调用方法,它指的就是哪个对象
成员变量和局部变量是可以同名的,使用时默认采取的是就近原则当成员变量与局部变量同名时,若想访问成员变量则this不能省略
在无参构造方法(成员变量的默认值,调用时自动默认,int为0,double为0.0)中可以调用其他方法(此时默认存在this关键字)关键字调用同方法中的其他成员方法,也能通过this(不可省)关键字调用本类中的有参构造方法
简单完整Student类
package oopbasic;
/** 创建学生类 */
public class Student {
// 成员变量
String name;
int age;
// 为成员变量赋默认值
String classNum;
String id;
// 无参沟造方法(默认系统给出,一旦有的有参构造,则不提供无参构造,需要用时手动给出,自动调用)
Student () {
// 设置默认值
name = "Unknown";
age = 0;
}
// 有参构造(用于在调用时给成员变量初始化)
Student (String name, int age, String classNum, String id) {
// 成员变量与局部变量同名(采取就近原则,当局部访问成员变量时,用this关键字)
// 变量初始化
this.name = name;
this.age = age;
this.classNum = classNum;
this.id = id;
}
// 成员方法
void study(){
System.out.println("学习java");
}
// 查看学生信息
void show() {
System.out.println("姓名:" + name + " 年龄:" + age + " 班级:" + classNum + " 学号:" + id);
}
// 包含参数的方法
void play(String anotherName) {
System.out.println(name + "和" + anotherName + "正在打球!");
}
}
测试类
package oopbasic;
public class Test {
public static void main(String[] args) {
// 无参构造
Student student = new Student();
// 访问成员变量
student.name = "张三";
student.age = 20;
student.classNum = "202301";
student.id = "001";
// 方法调用
student.study();
student.show();
student.play("李四");
// 有参构造
Student student1 = new Student("王五", 24, "2023", "002");
student1.study();
student1.show();
student1.play("李四");
}
}
4. java内存管理基础
Java内存管理:java内存由JVM(Java虚拟机)来管理 ▶ 方法区:java字节码文件(.class文件:包括方法信息,静态变量等)加载区 ▶ 堆(Heap):所有线程共享,并由垃圾收集器自动管理(存放new出来的对象,实例变量,数组元素,方法地址) ▶ 栈(Stack):存放线程方法调用和局部变量的内存区域 |
Java堆和栈的区别(Java中的堆和栈是两个不同的内存区域)
项目 | 堆 | 栈 |
---|---|---|
存储内容 | 存放动态分配的对象 | 存放线程的方法调用和局部变量 |
分配方式 | 堆的空间是动态分配的(动态地创建和销毁对象) | 栈的空间是静态分配的(线程在启动时就会为其分配一定大小的空间) |
生存周期 | 堆中的对象生存周期比较长(没有引用了才会收回) | 栈中的数据只存在于方法调用的过程中(执行完后即销毁) |
空间大小 | 堆远大于栈(堆需要存储所有动态分配的对象) | 栈的很小(存储方法参数、局部变量和返回地址等小数据) |
内存管理 | 堆由Java虚拟机自动进行垃圾回收,以释放不再使用的对象占用的空间 | 栈的内存管理是由Java编译器自动管理 |
5. 继承
继承:一个类(子类)继承另一个类(父类)的属性和方法,(继承机制是面向对象编程的基本特征之一)被继承的类称为父类或超类或基类(superclass),继承这个类的类称为子类或派生类(subclass) ▶ 关键字extends:使用关键字extends来建立继承关系(子类使用extends关键字后跟父类名称来声明继承关系) ▶ 特性:子类继承了父类的非私有属性和方法(公共(public)、受保护(protected)和默认(default)访问权限的成员),子类可以重写父类的方法 ▶ 单继承:一个类只能直接继承一个父类(Java不支持多重继承 —— 降低复杂性和避免潜在的冲突),但一个父类可以有多个子类 ▶ 传递性:类A继承自类B,而类B又继承自类C,则类A自动也继承了从类C继承来的属性和方法 ▶ 作用:实现代码复用,便于扩展代码和业务功能 |
通常,子类是对父类的抽象和扩展(子类相对于父类来说是更具体、更专门化的)
继承要符合是的关系
简单继承实例(this关键字实现)
- Person(父类)
package oop.extend;
public class Person {
String name;
int age;
String address;
void eat() {
System.out.println(name + "正在吃饭!");
}
void sleep() {
System.out.println(name + "正在睡觉!");
}
void sayHi(){
System.out.println("大家好,我叫" + name + ", 今年" + age + "岁了, 家住" + address);
}
}
- Student(子类1)
package oop.extend;
// 学生类继承Person
public class Student extends Person{
// 子类属性
String className;
String studentId;
// 无参构造
Student(){}
// 有参构造
Student(String name, int age, String address, String className, String studentId){
this.name = name;
this.age = age;
this.address = address;
this.className = className;
this.studentId = studentId;
}
// 子类方法
void study() {
System.out.println(name + "正在学习!");
}
}
- Teacher(子类2)
package oop.extend;
// 老师类继承Person
public class Teacher extends Person{
// 子类属性
double salary;
// 无参构造
Teacher(){}
// 有参构造
Teacher(String name, int age, String address,double salary){
this.name = name;
this.age = age;
this.address = address;
this.salary = salary;
}
// 子类方法
void teach() {
System.out.println(name + "正在上课!");
}
}
- TestExtend(测试类)
package oop.extend;
public class TestExtend {
public static void main(String[] args) {
System.out.println("学生************************************************************************************");
// 实例化学生对象(无参)
Student lm = new Student();
lm.name = "李明";
lm.age = 20;
lm.address = "北京市海淀区中关村大街123号";
lm.className = "2308";
lm.studentId = "200001";
// 方法调用
lm.eat();
lm.sayHi();
lm.sleep();
lm.study();
// 实例化学生对象(有参)
Student wq = new Student("王强", 22, "上海市浦东新区世纪大道789号", "2308", "200002");
// 方法调用
wq.eat();
wq.sayHi();
wq.sleep();
wq.study();
System.out.println("学生************************************************************************************");
System.out.println("老师************************************************************************************");
// 实例化老师对象(无参)
Teacher zw = new Teacher();
zw.name = "张伟";
zw.age = 26;
zw.address = "广州市天河区珠江西路456号";
zw.salary = 5600;
// 方法调用
zw.eat();
zw.sayHi();
zw.sleep();
zw.teach();
// 实例化老师对象(有参)
Teacher zm = new Teacher("赵敏", 25, "深圳市南山区科技园北区789号", 6000);
// 方法调用
zm.eat();
zm.sayHi();
zm.sleep();
zm.teach();
System.out.println("老师************************************************************************************");
}
}
super关键字
作用:指代当前对象的超类对象
super的用法:
1.super.成员变量名:访问超类的成员变量
2.super.方法():调用超类的方法
3.super():调用超类的构造方法
子类利用super调用父类的构造方法: 1. 在子类中无参构造器中,java默认自动调用父类的无参构造器(如果在子类中手动添加了有参构造,则子类不提供无参构造,同理也不会调用父类无参构造);
2. 如果父类创建了有参构造,则在父类中也要提供无参构造,不然子类中的有参和无参构造均会报错
简单继承实例(super关键字实现 – 继承父类构造器)
- Person(父类)
package oop.extend;
public class Person {
String name;
int age;
String address;
// 父类(无参)构造器
Person(){
System.out.println("父类无参构造");
}
// 父类(有)构造器
Person(String name, int age, String address){
this.name = name;
this.age = age;
this.address = address;
}
void eat() {
System.out.println(name + "正在吃饭!");
}
void sleep() {
System.out.println(name + "正在睡觉!");
}
void sayHi(){
System.out.println("大家好,我叫" + name + ", 今年" + age + "岁了, 家住" + address);
}
}
- Doctor(子类1:子类有参构造使用super关键字)
package oop.extend;
// 医生类继承Person
public class Doctor extends Person{
// 子类属性
String title;
// 无参构造
Doctor(){
// 默认调用父类的无参构造(super只能放在第一行),如果父类中写了有参构造(系统默认不提供无参构造),
// 则子类中无法调用无参构造(因为此时父类中没有无参构造),此时如果需要调用父类中的无参构造,则需要在父类中手动添加无参构造
super();
System.out.println("子类无参构造");
}
// 有参构造
Doctor(String name, int age, String address, String title){
// 将this改为super
// 指代当前对象的超类对象(super.成员变量名:访问超类的成员变量)代码标准格式(this指子类的,super指父类的)
super.name = name;
super.age = age;
super.address = address;
this.title = title;
}
// 子类方法
void cut(){
System.out.println(name + "正在做手术!");
}
// super.方法名():调用超类的方法
}
- Student(子类2:子类无参构造调用父类有参构造)
package oop.extend;
// 学生类继承Person
public class Student extends Person{
// 子类属性
String className;
String studentId;
// 无参构造
Student(){}
// 有参构造
Student(String name, int age, String address, String className, String studentId){
// 调用父类的有参构造器
super(name, age, address);
this.className = className;
this.studentId = studentId;
}
// 子类方法
void study() {
System.out.println(name + "正在学习!");
}
}
6. 方法重写
重写:重写指的是在子类中重新定义(覆盖)父类中已存在的方法 ▶ 特点:重写后的方法的名称、返回类型和参数列表都与父类的相同(改变方法体的功能) ▶ 方法权限修饰符:修饰符级别只能比父类低,不能超过父类 |
▶ 重载(Overloading)和重写(Overriding)
重载指的是在同一个类中,可以有多个方法具有相同的名称,但是参数列表不同。重载可以通过改变方法的参数个数、参数类型或者参数顺序来实现。在调用重载方法时,根据传入的参数来确定调用哪个方法。
重写指的是在子类中重新定义(覆盖)父类中已存在的方法。重写方法使用与父类方法相同的名称、返回类型和参数列表。重写方法不能拥有比父类更严格的访问修饰符,但可以拥有比父类更宽松的修饰符。
重载:提高程序的灵活性和可读性
重写:提高代码重用性和可维护性
- 重载
public class MyClass {
public void print(int num) {
System.out.println("打印整数:" + num);
}
public void print(String str) {
System.out.println("打印字符串:" + str);
}
public void print(double num1, double num2) {
System.out.println("打印两个浮点数:" + num1 + "," + num2);
}
}
- 重写
public class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗发出汪汪的声音");
}
}
final关键字
final 修饰类: final 修饰符应用于类时,表示最终类,不能被继承,无法扩展(继承)
final 修饰方法:final 修饰符应用于方法时,表示最终方法,不能被子类重写,子类中无法覆盖(重写)
final 修饰变量: final 修饰符应用于变量时,表示一个常量,只能被赋值一次,赋值后不可再修改
注:1. 不能被继承,但是可以继承其他类
2. 通常用大写字母表示常量名称如 final int NUMBER
简单继承实例(标准继承写法)
package working;
public class Work09 {
public static void main(String[] args) {
Dog husky = new Dog("哈士奇", 2, "灰色的");
husky.eat();
husky.drink();
husky.lookHome();
Dog shiba = new Dog("柴犬", 1, "黄色的");
shiba.eat();
shiba.drink();
shiba.lookHome();
Dog mastiff = new Dog();
mastiff.name = "藏獒";
mastiff.age = 3;
mastiff.color = "黑色的";
mastiff.eat();
mastiff.drink();
mastiff.lookHome();
Chick polish = new Chick("波尔鸡", 1, "黑色的");
polish.eat();
polish.drink();
polish.layEggs();
Chick phoenix = new Chick("龙凤鸡", 1, "金黄色的");
phoenix.eat();
phoenix.drink();
phoenix.layEggs();
Chick freeRange = new Chick();
freeRange.name = "土鸡";
freeRange.age = 1;
freeRange.color = "灰色的";
freeRange.eat();
freeRange.drink();
freeRange.layEggs();
Fish goldfish = new Fish("金鱼", 1, "金色的");
goldfish.eat();
Fish koi = new Fish();
koi.name = "锦鲤";
koi.age = 1;
koi.color = "红白相间的";
koi.eat();
}
}
// 父类
class Animal {
String name;
int age;
String color;
Animal() {
}
Animal(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
void drink() {
System.out.println(name + "在喝水!");
}
void eat() {
System.out.println(name + "在吃饭!");
}
}
// 子类:Dog
class Dog extends Animal {
Dog() {
}
Dog(String name, int age, String color) {
super(name, age, color);
}
void lookHome() {
System.out.println(age + "岁" + color + name + "正在看家!");
}
void eat() {
System.out.println(color + name + "在吃饭!");
}
}
// 子类:Chick
class Chick extends Animal {
Chick() {
}
Chick(String name, int age, String color) {
super(name, age, color);
}
void layEggs() {
System.out.println(age + "岁" + color + name + "在下蛋!");
}
void eat() {
System.out.println(color + name + "在啄米!");
}
}
// 子类:Fish
class Fish extends Animal {
Fish() {
}
Fish(String name, int age, String color) {
super(name, age, color);
}
void eat() {
System.out.println(color + name + "在吃鱼料!");
}
}
7. 抽象方法和抽象类
抽象方法:一种没有具体实现的方法(只有方法的声明,没有方法体,以();结束) ▶ 关键字:由abstract修饰 ▶ 抽象方法代表着不完整的方法,包含抽象方法的类一定是抽象类,抽象类不能实例化(new对象) ▶ 抽象类继承:抽象类使需要继承的(不是强制的),设计初衷是为了被其他类继承和扩展 ▶ 意义:定义一个接口或契约,要求子类必须实现该方法,使得代码更具可读性、可扩展性和可维护性 ▶ 抽象类是实现类和接口之间的桥梁:抽象类既可以实现接口可也以继承其他类 |
- 抽象方法意义
强制子类实现:抽象方法是一种约束,它要求继承抽象类或实现接口的子类必须提供具体的实现(确保子类中包含了抽象类或接口所定义的关键行为和功能)
提供统一的接口:抽象方法定义了一个统一的接口,使得不同的子类可以根据自身的特性来实现该接口(在多态的场景下,可以通过父类引用来调用不同子类的实现,提高代码的灵活性和扩展性)
实现代码重用:抽象方法可以在抽象类中定义,抽象类可以提供一些通用的属性和方法,而子类则只需实现特定的抽象方法(在子类中重复编写相同的代码,提高代码的复用性)
实现细节延迟到子类:抽象方法没有具体的实现,它将实现细节留给了具体的子类(父类提供了基本的框架和规范,而具体的实现可以适应各种不同的需求和场景)
抽象方法声明以分号结束,没有方法体;具体的实现需要在继承抽象类或实现接口的子类中完成
- 简单抽象方法
package oop.abstracts;
public class AbstractTest {
public static void main(String[] args) {
// 创建dog实例
Dog husky = new Dog("哈士奇");
husky.eat();
}
}
// 抽象类
abstract class Animal{
String name;
public Animal() {
}
public Animal(String name) {
this.name = name;
}
// 抽象方法(包含抽象方法的类是抽象类)
abstract void eat();
}
class Dog extends Animal{
public Dog() {
}
public Dog(String name) {
super(name);
}
// 抽象父类方法
@Override
void eat() {
System.out.println(name + "在卖力地吃着口狗粮");
}
}
8. 接口
接口(Interface):是一种抽象引用数据类型,用于定义类应该具有的方法和常量 ▶ 关键字:由interface定义 ▶ 作用:接口提供了一种契约机制,用于规定类应该如何实现特定的行为 ▶ 特点:接口不能被实例化,需要被实现 ▶ 实现类:实现接口的类,实现类中必须重写接口的所有抽象方法(接口中的方法默认都为抽象方法),且必须加上public关键字 ▶ 一个类可以实现多个接口(继承和实现,遵循java的先继承再实现) 接口是一种标准、规范,实现了该接口就具备该功能,若不实现该接口就不具备该功能,要使用该接口必就须遵循该接口的标准规范 接口是对java类继承的单根性扩展 —— 实现了多继承(java接口是支持多继承的) |
- 接口一般包含抽象方法,即java接口中的方法一般默认为(前面默认有abstract关键字)抽象方法(常量,默认方法,静态方法,私有方法等)
- 将所有子类所共有的属性和行为,抽到父类中(抽共性)
- 若子类的行为(类方法)/代码都一样,设计为普通方法
- 若子类的行为(类方法)/代码不一样,设计为抽象方法
- 将子类所共有的行为,抽到接口中
接口的实现,类的继承,方法的抽象
- Swimming接口
package oop.interfaces;
// 创建Swimming接口
public interface Swimming {
// 定义swim抽象方法(抽象方法没有{})
void swim();
}
- 父类Animal
package oop.interfaces;
// 创建动物类
public class Animal {
// 父类属性
String name;
int age;
String color;
// 父类无参构造器
Animal(){}
// 父类有参构造器
Animal(String name, int age, String color){
this.name = name;
this.age = age;
this.color = color;
}
void eat(){
System.out.println(name + "在吃饭");
}
}
- 子类1 Dog继承Animal实现接口Swimming
package oop.interfaces;
public class Dog extends Animal implements Swimming{
// 子类无参构造器
Dog(){}
// 子类有参构造器
Dog(String name, int age, String color){
// 引用父类有参构造器
super(name, age, color);
}
// 重写父类方法(功能拓展)
void eat(){
System.out.println(color + name + "在吃饭");
}
// 重写接口抽象方法
@Override
public void swim() {
System.out.println(age + "岁的" + name + "在游泳");
}
}
- 子类1 Fish继承Animal实现接口Swimming
package oop.interfaces;
public class Fish extends Animal implements Swimming{
// 子类无参构造器
Fish(){}
// 子类有参构造器
Fish(String name, int age, String color){
// 引用父类有参构造器
super(name, age, color);
}
// 重写父类方法(功能拓展)
void eat(){
System.out.println(color + name + "在吃饭");
}
// 重写接口抽象方法
@Override
public void swim() {
System.out.println(age + "岁的" + name + "在游泳");
}
}
- 员工管理简单设计图(类和接的继承以及接口的实现)
- 部分代码
package test;
public class CodeTiny06 {
public static void main(String[] args) {
Majordomo liming = new Majordomo();
liming.name = "李明总监";
liming.working();
liming.offDuty();
liming.completeTask();
liming.edit_ooks();
liming.solveProblem();
}
}
// 员工类
abstract class Employee{
String name;
int age;
double salary;
public Employee() {
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
void working(){
System.out.println("上班打卡");
}
void offDuty(){
System.out.println("下班打卡");
}
abstract void completeTask();
}
// 接口1
interface Books{
void edit_ooks();
}
// 接口2
interface Solve extends Books{
// 解决问题
void solveProblem();
// 培训员工
void train();
}
// 总监类的实现
class Majordomo extends Employee implements Solve{
// 无参
public Majordomo() {
}
// 有参
public Majordomo(String name, int age, double salary) {
super(name, age, salary);
}
// 父类方法重写
@Override
void completeTask() {
System.out.println("完成总监的工作");
}
// 接口2继承接口1的方法重写
@Override
public void edit_ooks() {
System.out.println(name + "在编辑书籍!");
}
// 接口2的方法重写
@Override
public void solveProblem() {
System.out.println(name + "解决问题");
}
@Override
public void train() {
System.out.println(name + "在培训员工");
}
}
// 其他教师类,项目经理类,班主任类的实现类似
9. 引用类型数组
引用类型数组:数据类型是引用数据类型的数组 |
- 引用类型数组的类型
package oop.quote;
// 创建引用类
public class Word {
// 单词
String word;
// 词性
String speech;
// 次数
int counts;
public Word() {
}
public Word(String word, String speech, int counts) {
this.word = word;
this.speech = speech;
this.counts = counts;
}
}
- 引用类型数组的实现
package oop.quote;
public class ReferArray {
public static void main(String[] args) {
String[] strings = {"接口", "是", "一种", "标准", "规范"};
String[] string = {"n", "v", "n", "n", "n"};
// 创建引用类型数组
Word[] words = new Word[5];
for (int i = 0; i < words.length; i++) {
words[i] = new Word(strings[i], string[i],1);
System.out.println(words[i].word + " " + words[i].speech + " " + words[i].counts);
}
}
}
10. 多态
➤ 多态:使用父类的引用来引用子类对象,并在运行时动态地确定具体调用的方法,进而实现多种形态 多态的两个关键点:继承和方法重写 核心:父类引用指向子类对象 ▶ 向上造型(Upcasting):一个子类对象赋值给一个父类引用变量的过程(多态性的特性) ▶ 向下转型(Downcasting):① 引用所指的对象是该类型;② 引用所指的对象实现了该接口或者继承了该类 多态的实际应用 ▶ 超类数组(实现代码复用):将不同子类对象统一封装到父类数组(引用类型(父类)数组,子类实例元素) ▶ 超类参数/返回值(参数多态,扩大应用范围)将父类作为参数或者返回值类型,进而出入/返回子类对象 |
package working;
import oop.polymorphism.Animal;
public class Work12 {
public static void main(String[] args) {
// 方法参数多态(向上造型)
Master master = new Master();
Dog1 dog1 = new Dog1("小黑",2,"黑");
Pandas panda = new Pandas("花花",3,"花");
Fishs fish = new Fishs("小金",1,"金");
master.feed(dog1);
master.feed(panda);
master.feed(fish);
// 数组元素多态(向上造型)
Animal1[] animals = new Animal1[5];
animals[0] = new Dog1("小黑", 2, "黑");
animals[1] = new Dog1("小白", 1, "白");
animals[2] = new Fishs("小金", 1, "金");
animals[3] = new Fishs("金鱼", 2, "金");
animals[4] = new Pandas("花花", 3, "黑白");
for (int i = 0; i < animals.length; i++) {
System.out.println(animals[i].name);
// 所有动物均会吃东西和喝水
animals[i].eat();
animals[i].drink();
// 狗的特有属性
if (animals[i] instanceof Dog1) {
Dog1 dog = (Dog1) animals[i];
dog.lookHome();
}
// 熊猫的特有属性
if (animals[i] instanceof Pandas) {
Pandas pandas = (Pandas) animals[i];
pandas.show();
}
// 拥有游泳特性的动物
if (animals[i] instanceof Swim) {
Swim s = (Swim) animals[i];
s.swim();
}
}
}
}
abstract class Animal1 {
String name;
int age;
String color;
Animal1() {
}
Animal1(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
void drink() {
System.out.println(color + "色的" + age + "岁的" + name + "正在喝水...");
}
abstract void eat();
}
interface Swim {
void swim(); //游泳
}
class Dog1 extends Animal1 implements Swim {
Dog1() {
}
Dog1(String name, int age, String color) {
super(name, age, color);
}
void lookHome() {
System.out.println(color + "色的" + age + "岁的狗狗" + name + "正在看家...");
}
void eat() {
System.out.println(color + "色的" + age + "岁的狗狗" + name + "正在吃骨头...");
}
public void swim() {
System.out.println(color + "色的" + age + "岁的狗狗" + name + "正在游泳...");
}
}
class Pandas extends Animal1 {
public Pandas() {
}
public Pandas(String name, int age, String color) {
super(name, age, color);
}
void show(){
System.out.println("熊猫" + name + "在表演");
}
@Override
void drink() {
super.drink();
}
@Override
void eat() {
System.out.println(color + "色的" + age + "岁的熊猫" + name + "正在吃竹子...");
}
}
class Fishs extends Animal1 implements Swim {
Fishs() {
}
Fishs(String name, int age, String color) {
super(name, age, color);
}
void eat() {
System.out.println(color + "色的" + age + "岁的小鱼" + name + "正在吃小虾...");
}
public void swim() {
System.out.println(color + "色的" + age + "岁的小鱼" + name + "正在游泳...");
}
}
class Master {
// (喂动物方法)传入父类类型(利用多态的特性,扩大了方法的应用范围 )-- 方法参数多态
void feed(Animal1 animal) {
// 子类继承父类的方法属性
animal.eat();
}
}
11. 内部类
➤ 内部类:内部类是定义在其他类内部的类 四种内部类:成员内部类、局部内部类、匿名内部类、静态内部类 特性:可以访问外部类的成员变量和方法,并且可以拥有自己的成员变量和方法(内部类通常只服务于外部类,对外不具备可见性) ▶ 内部类的意义:实现更好的封装性和组织代码结构(内部类可以访问外部类的私有成员,并且可以在需要时提供更好的代码逻辑组织方式) |
成员内部类(Member Inner Class)
成员内部类是定义在一个类的内部的普通类。它可以访问外部类的所有成员,包括私有成员
内部类的对象通常在外部类中创建
内部类中可以直接访问外部类的成员原因:在内部类中有个隐式的引用指向创建它的外部类对象
隐式的引用:外部类名.this
▶ 成员内部类使用场景:若A类只让B类用,并且A类还想访问B类的成员
▶ this:指代当前对象
▶ super:指代当前对象的超类对象
▶ 外部类名.this:指代当前对象的外部类对象
public class OuterClass {
// 外部类的代码
String name;
void methed(){
// 内部类对象通常在外部类中创建
InnerClass inners = new InnerClass();
}
public class InnerClass {
// 内部类的代码(可以直接使用外部类的属性方法)
System.out.println(name);
// 隐式的引用:外部类名.this
// 完整写法(上面省略了OuterClass.this.)
System.out.println(OuterClass.this.name);
}
}
局部内部类(Local Inner Class)
局部内部类是定义在方法内部或代码块内部的类
局部内部类的作用范围限定在所在的方法或代码块内部
局部内部类可以访问所在方法或代码块的局部变量,但是该局部变量必须是final或 effectively final 的
public class OuterClass {
// 外部类的代码
public void someMethod() {
// 方法代码
class LocalInnerClass {
// 局部内部类的代码
}
}
}
匿名内部类(Anonymous Inner Class)
匿名内部类是没有显式名称的内部类
匿名内部类通常用于实现接口或继承抽象类,并且只能创建该接口或抽象类的一个实例
匿名内部类可以直接在代码中创建,不需要先定义再使用
使用场景:创建一个派生类的对象,并且对象只创建一次
作用:简化代码
在匿名内部类中不能修改外面局部变量的值(在内部)
内部类也有独立的.class字节码文件外部类名$匿名内部类编号.class
- 匿名内部类
package oop.inners;
public class InnerClass {
public static void main(String[] args) {
// 匿名内部类(使用场景:创建一个派生类的对象,并且对象只创建一次)
/*
* new Inter(){}是一个(没有名字)的子类
* Inter(父类)类型 inter(Inter类型的引用) = new Inter(){}子类(多态)
* 只能创建一个对象,再次创建属于新的子类(与第一次建的子类不是同一个)
* Inter(父类) inter(父类类型的引用) =(指向) new Inter(){}(子类类体)(创建的子类);
*/
Inter inter = new Inter(){};
// 创建匿名内部类对象
Inters inters = new Inters(){
@Override
public void show() {
System.out.println("这是匿名内部类中的方法(来源于接口的方法重写)");
}
};
// 方法调用
inters.show();
}
}
// 接口Inter
interface Inter{
// 接口(抽象类)不能被实例化
}
// 接口Inters
interface Inters{
// 接口(抽象类)不能被实例化
// 抽象方法
void show();
}
静态内部类(Static Inner Class)
静态内部类是定义在其他类内部的静态类
静态内部类类似于外部类的静态成员,不依赖于外部类的实例
静态内部类可以直接通过外部类访问,不需要创建外部类的实例
public class OuterClass {
// 外部类的代码
public static class StaticInnerClass {
// 静态内部类的代码
}
}
12. package和import
➤ package:用于声明包 作用:避免类的命名冲突 规定:同包中的类不能同名,但不同包中的类可以同名 ▶ 类的全称:包名.类名,包名常常用层次结构 ▶ 建议:包名所有字母都小写 ➤ import:用于导入类 作用:访问不同包中的类,使代码简洁化 ▶ 建议:import导入类,再访问类 |
构造方法与普通方法
构造方法是一种特殊类型的方法,用于创建和初始化对象,在实例化类时被自动调用,与类的名称相同,并没有返回类型
方法名称与类名相同:构造方法的名称必须与类的名称完全相同,包括大小写
没有返回类型:与其他方法不同,构造方法不具有返回类型,甚至没有声明 void
不能使用 return 语句:由于构造方法没有返回类型,因此不能使用 return 语句
可以重载:你可以在一个类中定义多个构造方法,只要它们的参数列表不同
普通方法是用于执行类中特定任务或操作的常规方法。普通方法可以有任意的名称、参数和返回类型,并且与构造方法存在明显的区别。普通方法可以被对象调用,通过对象引用.方法名的形式来调用
13. 访问控制修饰符
➤ 访问控制修饰符指定了类、接口、变量、方法和构造函数的可见性和访问级别 作用:保护数据的安全(隐藏数据、暴露行为),实现封装 设计:数据(成员变量)私有化(private),行为(方法)大部分公开化(public) ▶ 类的访问权限:只能是public或默认的c ▶ 成员的访问权限:4种访问控制修饰符都可以 ➤ Java四种访问控制修饰符:private,默认的,protected,public ▶ public:公开的,任何类 ▶ private:私有的,本类 ▶ protected:受保护的,本类、派生类、同包类 ▶ default:默认的,什么也不写,本类同包类 |
标准JavaBean
- 属性私有
- 具有无参数的公共构造函数
- 提供公共的访问属性的get/set方法
- 标准的JavaBean
package oop.encapsulation;
public class Point {
/* 属性私有
* 具有无参数的公共构造函数
* 提供公共的访问属性的get/set方法
*/
// 属性私有
private int x;
private int y;
public Point() {
// 无参数的公共构造函数
}
public int getX() {
// 更好的保证数据的合法性(因为方法中可以做条件控制制)
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
标准JavaBean的命名和规范:
命名规范:
类名应该使用大写字母开头,且不包含下划线或其他特殊字符
应该具有无参数的公共构造函数
应该提供有意义的get/set方法,遵循命名规范
属性规范:
属性应该是私有的,并使用private修饰符进行封装
属性的命名应该使用小写字母开头,可以采用驼峰式命名规则
对于布尔类型的属性,get方法应该以is开头而不是get
公共方法规范:
应该提供公共的访问属性的get/set方法
可以根据需要添加其他自定义的公共方法
变量修饰符(访问权限修饰符、非访问权限修饰符):指定变量的访问权限和其他属性
访问权限修饰符:
private
:私有的变量只能在声明它们的类内部访问
protected
:受保护的变量可以在当前类、同一个包内的其他类以及子类中访问
public
:公共的变量可以在任何地方被访问
非访问权限修饰符:
final
:final修饰的变量表示常量,其值不能被修改
static
:static修饰的变量属于类级别,而不是实例级别,在类加载时初始化,并且可以通过类名直接访问
transient
:transient修饰的变量在序列化过程中会被忽略,不会被保存
volatile
:volatile修饰的变量用于多线程环境中,保证了对变量的可见性和禁止指令重排序优化
成员变量(实例变量和静态变量)
实例变量:没有static修饰,属于对象,存储在堆中,有几个对象就有几个实例变量,访问:引用/对象.变量名
静态变量:用static修饰,属于类,存储在方法区中,只有一个,访问:类名.变量名
,(对象所共享的数据)
静态代码块 由static修饰的代码块
特点:1. 属于类,在类被加载时自动执行,用于初始化静态成员变量或执行其他静态操作
2. 静态代码块只在类加载第一次时执行一次(第二次加载该类不执行),且只能执行静态操作
3. 静态代码块与静态成员变量、静态方法属于类级别(通过类名直接访问)
4. 静态代码块按照它们在类中出现的顺序依次执行(在无参构造方法之前执行)
- 静态变量
public class MyClass {
private static int count; // 静态变量
public static void main(String[] args) {
count = 10; // 直接访问静态变量
System.out.println("count = " + count);
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
obj1.count = 20; // 使用对象访问静态变量,不推荐
System.out.println("obj1.count = " + obj1.count);
System.out.println("obj2.count = " + obj2.count);
}
}
- 静态代码块
public class MyClass {
static {
// 静态代码块中的代码
System.out.println("静态代码块执行");
}
public static void main(String[] args) {
// main方法中的代码
System.out.println("main方法执行");
MyClass myClass = new MyClass();
}
}
静态方法 在类级别上定义的由static修饰的方法,不依赖于任何对象的实例
特点:1. 静态方法属于类(不依赖于任何具体的对象实例)
2. 静态方法只能访问静态成员(静态变量和静态方法)
3. 静态方法中不能有关键字this(this代表当前对象的引用,而静态方法不依赖于对象)
4. 静态方法可以直接从类中调用,无需创建对象实例(静态方法与对象无无关)
5. 静态方法不能被子类重写,但可以被继承并隐藏
- StaticMethod
package oop.encapsulation;
public class StaticMethod {
int n;
static int m;
void show(){
System.out.println(n);
System.out.println(m);
}
// 静态方法
static void test(){
// System.out.println(n);
System.out.println(m);
}
}
- StaticBlock
package oop.encapsulation;
public class StaticBlock {
String name;
int age;
static String className;
// 静态代码块的作用(初始化静态变量)
static {
// 只加载一次(如游戏的第一次资源加载,第二次启动时不再加载静态资源)
className = "2023001班";
}
// 无参构造
StaticBlock(){}
// 有参构造
StaticBlock(String name, int age){
this.name = name;
this.age = age;
}
void show(){
System.out.println("姓名:" + name + " 年龄:" + age + " 班级:" + className);
}
}
- StaticVar
package oop.encapsulation;
public class StaticVar {
// 实例变量
int a;
// 静态变量
static int b;
public StaticVar() {
a++;
b++;
}
void show() {
System.out.println("a的值:" + a + " b的值:" + b);
}
}
- Test
package oop.encapsulation;
public class Test {
public static void main(String[] args) {
// javabean
Point a = new Point();
// 设置a点的值
a.setX(100);
a.setY(200);
// 获取点a的值求和
int sum = a.getX() + a.getY();
System.out.println("和:" + sum);
// 静态变量
StaticVar var = new StaticVar();
var.show();
// 静态代码块
StaticBlock student = new StaticBlock("李四", 22);
student.show();
// 静态方法的访问
StaticMethod method = new StaticMethod();
// 通过对象访问普通方法show()
method.show();
// 通过类名访问静态方法test()
StaticMethod.test();
}
}
14. 常量和枚举
➤ 常量:由static和final修饰的成员变量 特性:1. 必须在声明的同时初始化 2. 通过类名来访问 类名.常量名 3. 常量的值一旦被初始化后就不能再修改 4. 常量通常用大写字母表示,多个单词之间使用下划线分隔 ▶ 常量在编译时会直接被替换成具体的数(效率高) ➤ 枚举(Enum):一种特殊的数据类型,用于表示一组具有固定数量和值的常量集合 ▶ 枚举类型:Java中被定义为一种特殊的类,其中每个枚举常量都是类的实例(枚举类型可以包含属性、方法、构造函数等成员) ▶ 特点:枚举不能继承其他类(默认已经继承了java.lang.Enum类) ▶ 枚举的构造方法都是私有的 |
- 简单枚举Seasons
package oop.Enums;
public enum Seasons {
// 季节的固定4个对象(常量)
SPRING, SUMMER, AUTUMN, WINTER;
}
- 复杂枚举Week
package oop.Enums;
public enum Week {
MONDAY("星期一", 1),
TUESDAY("星期二", 2),
WEDNESDAY("星期三", 3),
THURSDAY("星期四", 4),
FRIDAY("星期五", 5),
SATURDAY("星期六", 6),
SUNDAY("星期日", 7);
private String weekName;
private int num;
Week(String weekName, int num) {
this.weekName = weekName;
this.num = num;
}
public String getWeekName() {
return weekName;
}
public void setWeekName(String weekName) {
this.weekName = weekName;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
- 枚举测试
package oop.Enums;
public class Test {
public static void main(String[] args) {
// 获取某一元素做操作
Seasons season = Seasons.AUTUMN;
System.out.println("秋天:" + season);
// 获取所有对象
Seasons[] seasons = Seasons.values();
for (int i = 0; i < seasons.length; i++) {
switch (seasons[i]) {
case SPRING:
System.out.println("春天:" + seasons[i]);
break;
case SUMMER:
System.out.println("夏天:" + seasons[i]);
break;
case AUTUMN:
System.out.println("秋天:" + seasons[i]);
break;
case WINTER:
System.out.println("冬天:" + seasons[i]);
}
}
// 复杂枚举
Week week = Week.MONDAY;
System.out.println(week.getWeekName() + week.getNum());
Week[] weeks = Week.values();
for (int i = 0; i < weeks.length; i++) {
System.out.println(weeks[i].getWeekName() + " 第" + weeks[i].getNum() + "天");
}
}
}