封装
面向对象程序的三大特点:封装,继承,多态
通过封装可以提高程序的安全性,通过继承可以实现代码的复用,通过多态可以提高程序的可扩展性
为什么封装?
允许用户直接修改属性,用户可能会赋值一些无效的值,所以要对属性进行封装
如何封装?
使用 private
修饰属性为私有的,private
私有成员只能在当前类体中使用
提供公共的 getter
setter
方法实现对属性的访问,必要时可以在 setter
方法中对参数接收的数据有效性进行验证
/**
* 封装
*/
public class Person {
// 使用 private 关键字将属性修饰为私有属性
private String name;
private int age;
private String gender;
// getter 方法返回属性
public String getName() {
return name;
}
// setter 方法设置属性
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 对数据有效性进行验证
if (age >= 0 && age <= 130) {
this.age = age;
}else {
System.out.println(age + "超出类人的年龄范围");
}
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
// 对数据有效性进行验证
if ("男".equals(gender) || "女".equals(gender)) {
this.gender = gender;
}else {
System.out.println(gender + "不是合理的性别");
}
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
// setter 方法设置属性
p1.setName("zhangsan");
p1.setAge(999);
p1.setGender("test");
// getter 方法获取属性
System.out.println(p1.getName());
System.out.println(p1.getAge());
System.out.println(p1.getGender());
}
}
输出结果:
999超出类人的年龄范围
test不是合理的性别
zhangsan
0
null
以上输入结果中,数据有效性验证失败的数据会输出为默认值
继承
继承使用 extends
关键字
子类继承父类,就自动拥有了父类的实例变量与实例方法
/**
* 定义宠物类
*/
public class Pet {
String nickname;
int age;
public void eat() {
System.out.println(nickname + "在吃饭");
}
}
/**
* 定义小狗类,继承宠物类
*/
public class Dog extends Pet {
// 定义子类特有的属性
boolean specialDog;
// 定义子类特有的方法
public void guard() {
System.out.println(nickname + "在看家");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// 子类对象访问继承自父类的属性
dog.nickname = "小黑";
dog.age = 5;
// 子类对象调用继承自父类的方法
dog.eat();
// 子类对象访问子类特有的属性
dog.specialDog = true;
// 子类对象调用子类特有的方法
dog.guard();
}
}
输出结果:
小黑在吃饭
小黑在看家
构造方法不能继承
执行子类的构造方法时,会先调用父类的构造方法
在子类构造方法体中,默认调用父类的无参构造方法,如果父类没有无参构造方法,则语法错误
在子类的构造方法中,可以通过 super()
方法显示的调用指定的构造方法
/**
* 定义人类
*/
public class Person {
// 定义属性
String name;
int id;
int age;
String gender;
// 定义无参构造方法
public Person() {
System.out.println("我是父类的无参构造方法");
}
// 定义有参构造方法
public Person(String name, int id, int age, String gender) {
System.out.println("我是父类的有参构造方法");
this.name = name;
this.id = id;
this.age = age;
this.gender = gender;
}
}
/**
* 定义学生类,继承自人类
*/
public class Student extends Person {
// 定义子类特有的属性
int score;
String major;
// 定义无参构造方法
public Student() {
// 这里没有使用 super() 来指定调用父类的哪个构造方法,会默认调用父类的无参构造方法
System.out.println("我是子类的无参构造方法");
}
public Student(String name, int id, int age, String gender, int score, String major) {
// 这里使用 super() 方法指定了调用父类的有参构造方法
super(name, id, age, gender);
this.score = score;
this.major = major;
System.out.println("我是子类的有参构造方法");
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student();
Student student2 = new Student("小明", 001, 18, "男", 99, "计算机");
}
}
输出结果:
我是父类的无参构造方法
我是子类的无参构造方法
我是父类的有参构造方法
我是子类的有参构造方法
方法重写,当继承自父类的方法无法满足子类的需求时,可以在子类中重写该方法
/**
* 定义人类
*/
public class Person {
// 定义属性
String name;
int id;
int age;
String gender;
// 定义无参构造方法
public Person() {
}
// 定义有参构造方法
public Person(String name, int id, int age, String gender) {
this.name = name;
this.id = id;
this.age = age;
this.gender = gender;
}
// 定义方法显示人的信息
public void show() {
System.out.println("name: " + name);
System.out.println("id: " + id);
System.out.println("age: " + age);
System.out.println("gender: " + gender);
}
}
/**
* 定义学生类,继承自人类
*/
public class Student extends Person {
// 定义子类特有的属性
int score;
String major;
// 定义无参构造方法
public Student() {
}
public Student(String name, int id, int age, String gender, int score, String major) {
// 这里使用 super() 方法指定了调用父类的有参构造方法
super(name, id, age, gender);
this.score = score;
this.major = major;
}
// 继承的 show() 方法,可以显示 name,id,age,gender 无法显示 score,major 在这里重写 show() 方法
@Override // 注解,让编译器验证方法重写是否符合规则
public void show() {
// 调用继承的 show() 方法
super.show();
// 添加功能
System.out.println("score: " + score);
System.out.println("major: " + major);
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小明", 002, 19, "男", 100, "java");
// 通过子类对象调用 show() 方法,会调用子类中重写之后的 show() 方法,这是 java 的就近原则
student.show();
}
}
输出结果:
name: 小明
id: 2
age: 19
gender: 男
score: 100
major: java
静态方法不存在继承问题,属于哪个类的静态方法就使用哪个类的命名调用即可
访问权限
类的访问权限
类的访问权限分为两种:公共类和非公共类
如果想要这个类在其他包中使用,需要声明为 public
公共类,公共类的类名和源文件的名称必须一致,一个源文件中只能定义一个公共类
如果类没有使用 public
修饰,则只能在当前包中使用
类成员的访问权限
类成员的访问权限有四种:
访问权限 | 当前类 | 当前包 | 不在当前包的子类 | 不在当前包也没有继承关系的类 |
---|---|---|---|---|
private 私有的 |
yes | no | no | no |
默认权限 | yes | yes | no | no |
protected 保护的 |
yes | yes | yes | no |
public 公共的 |
yes | yes | yes | yes |
方法覆盖的规则
在子类中重写父类的方法,校验规则:
- 方法签名必须一致,方法签名就是方法名与参数个数以及参数的类型
- 方法返回值类型可以一致,也可以是子类型
- 方法的访问权限可以一致,也可以更大
- 方法抛出的异常,可以一致,也可以是子异常
Object
类
Object
类是所有 java
类的根父类
Java
中的类如果没有通过 extends
继承父类,则该类默认继承 Object
类
继承是可传递的,所以 Object
类的中定义的方法,所有类都可以继承到
Object
类中定义的方法:
Modifier and Type | Method and Description |
---|---|
protected Object |
clone() 客隆堆中的对象 |
boolean |
equals(Object obj) 判断堆中两个对象的属性值是否一致 |
Class<?> |
getClass() 返回对象的类 |
int |
hashCode() 返回对象的哈希码 |
void |
notify() 唤醒等待的线程 |
String |
toString() 把对象转换为字符串 |
void |
wait() 线程等待 |
实体类的定义规范
实体类是指客观存在的实体,如,人,电脑,书
在定义实体类时,有以下定义规范:
- 把属性定义为私有的
- 提供无参构造方法
- 重写
equals()
hashCode()
- 重写
toString()
- 提供
getter
setter
方法
public class Book {
// 定义私有属性
private String name;
private String author;
private String press;
private String isbn;
private double price;
// 定义无参构造方法
public Book() {
}
// 重写 equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return name.equals(book.name) &&
isbn.equals(book.isbn);
}
// 重写 hashCode
@Override
public int hashCode() {
return Objects.hash(name, isbn);
}
// 重写 toString
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
", press='" + press + '\'' +
", isbn='" + isbn + '\'' +
", price=" + price +
'}';
}
// 定义 getter 和 setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPress() {
return press;
}
public void setPress(String press) {
this.press = press;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
final
关键字
final
关键字可以修饰类,修饰属性,修饰方法,修饰局部变量,修饰形参
final
修饰的类为最终类,不能被继承
final
修饰的属性(字段)
- 必须显示初始化,如果没有显示初始化,需要在构造方法中赋值,因为显示初始化在编译时会编译到构造方法中,所以,显示初始化与在构造方法中赋值效果一样
- 不能重新赋值
- 经常与
static
关键字一起修饰属性(字段),称为final
常量,如static final double PI = 3.14
常量名一般大写
final
修饰的方法为最终方法,不能被重写
final
修饰的局部变量,不能重新赋值
final
修饰的形参,不能重新赋值
多态
多态分为对象多态和行为多态。对象多态是指同一个对象名可以引用不同的对象,行为多态是指调用同一个方法会执行不同的方法体。
对象多态是通过对象转型实现的
行为多态分为静态多态和动态多态。静态多态是通过方法重载实现,在编译阶段就可以根据方法的实参来确定调用哪个方法,又称为编译多态。动态多态是在程序运行过程中,确定要执行哪个对象的方法,又称为运行时多态或者动态绑定。
对象向上转型
对象向上转型是指可以给父类的引用赋值子类对象,这是自动类型转换
动态绑定
实现动态绑定有四个条件:
- 子类继承父类
- 子类重写父类方法
- 给父类的引用赋值子类对象
- 通过父类的引用调用被重写的方法
对象向下转型
给父类的引用赋值子类的对象,是对象向上转型。在对象向上转型之后,即父类的引用指向了子类在堆中的对象,此时如果想要访问子类特有的属性或者调用子类特有的方法时,就需要对象向下转型。
对象向下转型通过强制类型转换来实现
/**
* 定义动物类做为父类
*/
public class Animal {
public void eat(){
};
}
/**
* 定义小狗类做为子类
*/
public class Dog extends Animal {
// 重写父类的 eat() 方法
@Override
public void eat() {
System.out.println("小狗喜欢吃骨头");
}
// 给小狗添加特有的方法
public void guard() {
System.out.println("小狗可以看家");
}
}
/**
* 定义小猫类做为子类
*/
public class Cat extends Animal {
// 重写父类的 eat() 方法
@Override
public void eat() {
System.out.println("小猫喜欢吃鱼");
}
// 给小猫添加特有的方法
public void catchMouse() {
System.out.println("小猫可以捉老鼠");
}
}
/**
* 对象向上转型
*/
public class Test01 {
public static void main(String[] args) {
// 给父类的引用赋值子类 Dog 的对象
Animal animal = new Dog();
// 给父类的引用赋值子类 Cat 的对象
animal = new Cat();
}
}
/**
* 动态绑定
*/
public class Test02 {
public static void main(String[] args) {
// 给父类引用赋值子类对象
Animal animal = new Dog();
// 通过父类引用调用被重写的方法,输出结果:小狗喜欢吃骨头
animal.eat();
// 给父类引用赋值子类对象
animal = new Cat();
// 通过父类引用调用被重写的方法,输出结果:小猫喜欢吃鱼
animal.eat();
}
}
/**
* 对象向下转型
*/
public class Test03 {
public static void main(String[] args) {
Animal animal = new Dog();
// 父类引用不能直接调用子类对象特有的方法,需要通过强制类型转换实现对象向下转型
((Dog)animal).guard();
animal = new Cat();
((Cat)animal).catchMouse();
doSomething(new Dog());
doSomething(new Cat());
}
// 一般情况下,对象向下转型前,会使用 instanceof 运算符判断堆中对象是否为目标类型
public static void doSomething(Animal animal) {
if (animal instanceof Dog) {
((Dog)animal).guard();
}else if (animal instanceof Cat) {
((Cat)animal).catchMouse();
}
}
}
多态的作用
多态可以提供程序的可扩展性,体现面向抽象的编程思想,降低代码的耦合度
/**定义宠物类为父类
*
*/
public class Pet {
String name;
public Pet(String name) {
this.name = name;
}
}
/**
* 定义小狗类为子类
*/
public class Dog extends Pet {
public Dog(String name) {
super(name);
}
}
/**
* 定义小猫类为子类
*/
public class Cat extends Pet {
public Cat(String name) {
super(name);
}
}
/**
* 定义宠物医院类
*/
public class PetHospital {
public void treat(Pet pet) {
System.out.println(pet.name + "正在宠物医院玩耍");
}
}
public class Test {
public static void main(String[] args) {
PetHospital hospital = new PetHospital();
hospital.treat(new Dog("小黑"));
hospital.treat(new Cat("小花"));
}
}
输出结果:
小黑正在宠物医院玩耍
小花正在宠物医院玩耍
抽象类
什么是抽象类?
使用 abstract
修饰的类,就是抽象类
为什么要定义抽象类?
含有抽象方法的类,必须定义为抽象类。对事物进行更高层级的抽象时,如宠物类,交通工具类等无法具体化的类,就可以定义为抽象类
为什么定义抽象方法?
当类有某个操作,但是无法具体实现时就可以定义为抽象方法
抽象方法的特点:
使用
abstract
修饰,没有方法体含有抽象方法的类,必须定义为抽象类
抽象类的特点:
- 只要是由
abstract
修饰的类,就是抽象类 - 抽象类可以没有抽象方法
- 子类继承了抽象类,需要重写抽象方法,如果没有重写,则子类也需要定义为抽象类
- 抽象类不能实例化
- 抽象类的引用可以赋值子类对象,也可以赋值匿名内部类对象
- 抽象类中除了可以定义抽象方法,也可以定义普通类的成员,如实例变量,实例方法,静态变量,静态方法,构造方法等
匿名内部类
匿名内部类是一个没有类名的内部类,如果需要一个只使用一次的子类,可以定义为匿名内部类,就不需要再创建一个子类了,匿名内部类只能使用一次
/**
* 把马戏团的所有动物抽象为动物类
*/
public abstract class Animal {
// 马戏团所有的动物都会表演
public abstract void play();
}
/**
* 定义狗熊类,继承动物类
*/
public class Bear extends Animal {
@Override
public void play() {
System.out.println("狗熊表演跳绳");
}
/**
* 定义大象类,继承动物类
*/
public class Elephant extends Animal {
@Override
public void play() {
System.out.println("大象表演跳舞");
}
}
/**
* 定义猴子类,继承动物类
*/
public class Monkey extends Animal {
@Override
public void play() {
System.out.println("猴子表演骑自行车");
}
}
/**
* 训练师
*/
public class Trainner {
public void train(Animal animal) {
System.out.println("训练师请动物出来表演节目");
animal.play();
}
}
public class Test {
public static void main(String[] args) {
Trainner trainner = new Trainner();
// 给抽象类传递子类对象
trainner.train(new Bear());
trainner.train(new Elephant());
trainner.train(new Monkey());
//给抽象类传递匿名内部类
trainner.train(new Animal() {
@Override
public void play() {
System.out.println("老虎表演跳火圈");
}
});
}
}
执行结果:
训练师请动物出来表演节目
狗熊表演跳绳
训练师请动物出来表演节目
大象表演跳舞
训练师请动物出来表演节目
猴子表演骑自行车
训练师请动物出来表演节目
老虎表演跳火圈
接口
接口是什么
电脑上面有 USB 接口,HDMI 接口,耳机接口等接口,这些接口可以扩展电脑的功能
Java 中的接口可以简单的理解为功能的封装,用来扩展类的功能
接口的定义与实现
定义接口的语法
[修饰符] inter 接口名 {
功能用方法表示
}
实现接口的语法
class 类名 implements 接口名 {
在类中重写接口的抽象方法
}
说明
- 接口可以简单的看作是功能的封装,功能使用方法来表示,接口中的方法默认使用
public
abstract
修饰,接口也可以看作是一组操作规范或者说一个操作协议 - 类实现接口,需要重写接口的抽象方法,如果不重写,则需要将类定义为抽象类
- 接口也是一种引用数据类型,可以定义变量,但是接口不能实例化对象,接口的引用可以赋值实现类的对象或者匿名内部类对象
- 通过接口的引用调用抽象方法时,实际执行的是实现类对象的方法,称为接口多态
- 除了默认的抽象方法,jdk8 在接口中新增了
default
修饰的方法和static
修饰的方法,这两种方法经常用于项目升级时扩展接口的功能 - 接口也能继承,并且接口允许多继承
- 接口允许多实现,即一个类在继承父类的同时,可以实现多个接口,需要重写所有接口的抽象方法
例子1
/**
* 定义一个飞行接口,封装一个飞行功能
*/
public interface Flyable {
// 功能用方法表示,接口中的方法,默认为 public abstract 修饰的抽象方法
void fly();
}
/**
* 类实现接口
*/
public class RedBird implements Flyable {
// 类实现接口需要重写接口的抽象方法,如果不重写,那么要把类定义为抽象类
@Override
public void fly() {
System.out.println("红火正常飞行");
}
}
/**
* 类实现接口
*/
public class BlueBird implements Flyable {
// 类实现接口需要重写接口的抽象方法,如果不重写,那么要把类定义为抽象类
@Override
public void fly() {
System.out.println("蓝冰在飞行过程中可以一个变三个");
}
}
public class Test {
public static void main(String[] args) {
// 接口属于引用数据类型,可以定义变量
Flyable f;
// 给接口的引用赋值实现类 RedBird 的对象
f = new RedBird();
// 调用 RedBird 实现类重写的 fly() 方法
f.fly();
// 给接口的引用赋值实现类 BlueBird 的对象
f = new BlueBird();
// 调用 BlueBird 实现类重写的 fly() 方法
f.fly();
// 也可以赋值匿名内部类对象
f = new Flyable() {
@Override
public void fly() {
System.out.println("黑风会在飞行过程中爆炸");
}
};
// 调用匿名内部类重写的 fly() 方法
f.fly();
}
}
执行结果:
红火正常飞行
蓝冰在飞行过程中可以一个变三个
黑风会在飞行过程中爆炸
例子2
/**
* 定义接口
*/
public interface MyInterface {
// 接口中的字段,默认为 public static final 修饰的常量
int X = 123;
// 接口中的方法,默认为 public abstract 修饰的抽象方法
void m1();
// default 修饰的方法,可以有方法体,在实现类中,可以重写也可以不重写该方法
default void dm() {
System.out.println("default修饰的方法,可以有方法体");
}
// static 修饰的方法
static void sm() {
System.out.println("static 修饰的方法");
}
}
/**
* 定义接口的实现类
*/
public class MyclassImpl implements MyInterface {
@Override
public void m1() {
System.out.println("在实现类中重写接口的抽象方法");
}
@Override
public void dm() {
System.out.println("在实现类中可以重写 default 方法,也可以不重写");
}
}
public class Test {
public static void main(String[] args) {
// 接口中的抽象方法,需要通过接口的引用来调用
MyInterface ref = new MyclassImpl();
ref.m1();
// 接口中的常量,只能通过接口名访问
System.out.println(MyInterface.X);
// default 修饰的方法,通过接口的引用调用
ref.dm();
// 接口中的静态方法,通过接口名调用
MyInterface.sm();
}
}
执行结果:
在实现类中重写接口的抽象方法
123
在实现类中可以重写 default 方法,也可以不重写
static 修饰的方法
抽象类与接口的区别
- 本质不同,抽象类是对事物的抽象,解决对象到底是什么;接口是对功能的封装,解决对象能做什么
- 内容不同,抽象类除了抽象方法外,可以定义实例变量,实例方法,静态变量,静态方法,构造方法等;接口除了抽象方法外,可以定义
public static final
常量,default
方法,静态方法 - 使用方式不同,抽象类需要被
extends
继承,接口需要implements
实现,类只允许单继承,接口可以多继承 - 抽象类是由子类对象或者匿名内部类对象作为实参,由父类的引用作为的形参;接口是由实现类对象或者匿名内部类对象作为实参,由接口的引用作为形参
提倡面向接口编程
- 接口更灵活,一个类在继承另外一个父类的同时,可以实现多个接口
- 接口体现面向抽象编程的思想,降低类的耦合度,可以提高程序的可扩展性
- 接口可以使项目分层
对象数组
定义数组
数据类型[] 数组名 = new 数据类型[长度];
数据类型就是数据中存储数据的类型,如果数组存储整数则定义为 int
类型,存储字符串则定义为 String
类型,存储 Person
对象则定义为 Person
类型
如果数组中存储的是引用类型的对象,则该数组称为对象数据
public class Student {
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name=" + name + ",age=" + age + "}";
}
}
public class Test {
public static void main(String[] args) {
// 定义数组,存储 Student 对象
Student [] data = new Student[10];
// 在使用对象数据是,一般会定义一个变量记录数组中元素数量
int size = 0;
// 向数组中添加 Student 对象的引用,每添加一个元素,size 的值就会加 1
data[size] = new Student("小明", 18); size++;
data[size] = new Student("小花", 18); size++;
data[size] = new Student("小六", 18); size++;
// 遍历数组中所有元素
System.out.println("数组中的所有元素:");
for (int i = 0; i < size; i++) {
System.out.println(data[i]);
}
// 删除数组中的小刘同学
// 1. 找出小六的下标
int x = -1;
for (int i = 0; i < size; i++) {
if ("小六".equals(data[i].name)) {
x = i;
break;
}
}
// 2. 把数组中从 x+1 开始的每个元素前移
System.arraycopy(data, x+1, data, x, size-x-1);
// 3. 元素的数量 size 减 1
size--;
// 4. 最后一个元素原来占用的空间现在空出来了,需要释放掉,把最后一个元素赋值为 null
data[size] = null;
System.out.println("删除小六同学之后数组中的元素:");
for (int i = 0; i < size; i++) {
System.out.println(data[i]);
}
}
}
执行结果:
数组中的所有元素:
Student{name=小明,age=18}
Student{name=小花,age=18}
Student{name=小六,age=18}
删除小六同学之后数组中的元素:
Student{name=小明,age=18}
Student{name=小花,age=18}