面向对象是一种以对象为中心的编程思想,通过指挥对象实现具体的功能
1. 类和对象
类:类是现实世界中一种具有共同行为或者属性的事物的抽象。
对象:对象就是指在世界里真实的实体。
简单理解:类是对事物的一种描述,对象则为具体存在的事物
2. 封装
在编程中,封装(encapsulation)是一个核心概念,它确保了对象的内部状态不会被外部代码随意更改。这意味着对象的状态是私有的,外部代码只能通过定义好的接口来交互,而不能直接操作。封装的实践有助于保护对象的状态,同时清晰地区分公共接口和私有状态,通常是同(Private)关键字来进行声明。
3. 继承
在面向对象编程中,对于具有相似属性和方法的类,例如猫和狗都具备颜色属性以及跑跳等行为,且这些实现大致相同,每次为不同动物编写独立类会非常繁琐。为了简化这一过程并提高代码的复用性,我们可以使用继承。比如,可以创建一个名为Animal的基类,包含所有动物共有的属性和方法,然后让各个子类继承这个基类,从而获得这些属性和方法。
// 定义一个 Animal 类作为父类
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// 定义一个 Cat 类继承 Animal 类
class Cat extends Animal {
public Cat(String name) {
super(name); // 调用父类的构造方法
}
public void meow() {
System.out.println(name + " is meowing.");
}
}
// 定义一个 Dog 类继承 Animal 类
class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类的构造方法
}
public void bark() {
System.out.println(name + " is barking.");
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat("Kitty");
cat.eat(); // 调用父类的方法
cat.meow(); // 调用子类的方法
Dog dog = new Dog("Buddy");
dog.eat(); // 调用父类的方法
dog.bark(); // 调用子类的方法
}
}
继承的特点:单继承,多层继承
子类能够继承父类的属性和法,但是当父类的方法不是子类所需要的方法时,子类就可以重写父类的方法。
重写的特点:
- 子类重写的方法名和参数列表(包括参数类型)和父类完全一样
class Animal {
public void makeSound() {
System.out.println("Animal is making a sound");
}
}
class Dog extends Animal {
// 重写父类方法,方法签名必须相同
@Override
public void makeSound() {
System.out.println("Dog is barking");
}
}
- 父类声明为私有的方法不可重写,被static和final修饰一样不能重写
class Animal {
public static void makeSound() {
System.out.println("Animal is making a sound");
}
public final void sleep() {
// 休眠一段时间
}
}
class Dog extends Animal {
// 编译错误:不能重写被 final 修饰的方法
// @Override
// public void sleep() {
// // 休眠一段时间
// }
// 编译错误:不能重写被 static 修饰的方法
// @Override
// public static void makeSound() {
// System.out.println("Dog is barking");
// }
}
- 子类方法的访问权限不能更低(例如父类为public 子类不能为private)
class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
// 缩小访问修饰符是不允许的
// @Override
// private void eat() {
// System.out.println("Dog is eating");
// }
}
- 子类方法不能抛出比父类方法更宽泛的异常
class Animal {
public void sleep() throws InterruptedException {
// 休眠一段时间
}
}
class Dog extends Animal {
// 编译错误:子类方法不能抛出比父类方法更宽泛的异常
// @Override
// public void sleep() throws Exception {
// // 休眠一段时间
// }
}
4. 多态
通过上述例子,我们了解到可以通过继承来重用父类的属性或方法,这是因为我们的方法需求各不相同。那么,如果我们需要一个方法能够接收不同的对象并执行不同的操作,我们该如何实现呢?这就涉及到了多态的概念,多态允许同一个对象在不同的时刻展现出多种形态。
要想实现多态,有几个前提。
- 要有继承或实现关系
- 要有方法的重写
- 要有父类引用指向子类对象
class Animal {
public void eat(){
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test1Polymorphic {
/*
多态的前提:
1. 要有(继承 \ 实现)关系
2. 要有方法重写
3. 要有父类引用, 指向子类对象
*/
public static void main(String[] args) {
// 当前事物, 是一只猫
Cat c = new Cat();
// 当前事物, 是一只动物
Animal a = new Cat();
a.eat();
}
}
在访问成员的时候
成员变量:编译看父类,运行看父类
成员方法:编译看父类,运行看子类
class Fu {
int num = 10;
public void method(){
System.out.println("Fu.. method");
}
}
class Zi extends Fu {
int num = 20;
public void method(){
System.out.println("Zi.. method");
}
}
public class Test2Polymorpic {
/*
多态的成员访问特点:
成员变量: 编译看左边 (父类), 运行看左边 (父类)
成员方法: 编译看左边 (父类), 运行看右边 (子类)
*/
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);
f.method();
}
}
好处:使用多态的好处十分明显,在定义方法时,直接使用父类作为参数,在使用时用具体的子类类型参与操作。
缺点:不能使用子类的特有成员。
为了克服无法使用子类成员的限制,我们可以通过向下转型来访问子类的成员。这类似于我们之前学习的基本数据类型的强制转换,不同之处在于这次转换的是引用类型。子类型 对象名 = (子类型)父类引用;然而,就像基本数据类型强制转换可能导致精度降低一样,向下转型也有风险。如果尝试转换的对象不是该父类的子类实例,而是其他对象,就会抛出ClassCastException。因此,在转型前应进行检查,确认传入的变量确实是目标类型的实例,这可以通过使用(instanceof)关键字来实现。
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome(){
System.out.println("看家");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test4Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}
public static void useAnimal(Animal a){ // Animal a = new Dog();
// Animal a = new Cat();
a.eat();
//a.watchHome();
// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 类型转换异常
// 判断a变量记录的类型, 是否是Dog
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
5. 抽象类和接口
抽象类
抽象类是什么?正如其名,它是一种抽象的类。在对比具体的概念时,具体类通常与直接的对象相对应,而抽象类则不会。它代表了一个抽象的概念。当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了。如果一个类不包含足够的信息来描述一个具体的对象,那么这个类就是抽象类,并且会用abstract关键字来修饰。当一个类不够具体时,就可以使用抽象类来表示更高层次的抽象。
抽象类的特点:
- 构造方法:不能修饰构造方法
- 构造块:不能修饰构造块
- 属性:不能修饰属性
- 方法:修饰的方法不能写代码,为模板作用,只能被子类继承重写
- 继承:
1. 可以被abstract类继承,继承过去的抽象方法可以选择性重写
2. 被普通类继承时,必须重写抽象方法,或者变为抽象类
3. 抽象类本身不能实例化 - 类:可被修饰类,但不能被实例化,只能被继承,为模板作用,继承的类需要重写他的抽象方法
// 动物类
public abstract class Animal {
public void drink(){
System.out.println("喝水");
}
public Animal(){
}
public abstract void eat();
}
// 猫类
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
// 狗类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
// 测试类
public static void main(String[] args) {
Dog d = new Dog();
d.eat();
d.drink();
Cat c = new Cat();
c.drink();
c.eat();
//Animal a = new Animal();
//a.eat();
}
猫狗存在共性内容,那就是eat,向上抽取出一个动物类(Animal)。但是父类无法将eat方法具体描述清楚,所以将其定义为抽象方法,实现只靠子类实现。
接口
接口(Interface)是 Java 编程语言中的一种引用类型,它是一种抽象的类型,定义了一组方法的规范,而不包含实现。在 Java 中,接口是实现多态和解耦的重要手段之一。以下是关于接口的详细描述:
- 接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。
- Java中接口存在的两个意义
- 用来定义规范
- 用来做功能的拓展
- 接口不能实例化
我们可以创建接口的实现类对象使用 - 接口的子类
要么重写接口中的所有抽象方法
要么子类也是抽象类
// 接口
public interface Inter {
public static final int NUM = 10;
public abstract void show();
}
// 实现类
class InterImpl implements Inter{
public void method(){
// NUM = 20;
System.out.println(NUM);
}
public void show(){
}
}
// 测试类
public class TestInterface {
/*
成员变量: 只能是常量 系统会默认加入三个关键字
public static final
构造方法: 没有
成员方法: 只能是抽象方法, 系统会默认加入两个关键字
public abstract
*/
public static void main(String[] args) {
System.out.println(Inter.NUM);
}
}
6. 代码块
在Java中,使用 { } 括起来的代码被称为代码块
- 局部代码块:
在方法中出现,用来限定变量的生命周期,及早释放,提高内存的利用率。 - 构造代码块:
在类中方法外出来呢,每次调用构造方法时都会执行,并且先于构造方法执行。 - 同步代码块:
同步代码块指的是被Java中Synchronized关键词修饰的代码块,在Java中,Synchronized关键词不仅仅可以用来修饰代码块,与此同时也可以用来修饰方法,是一种线程同步机制,被Synchronized关键词修饰的代码块会被加上内置锁。 - 静态代码块:
在类中方法外出现,使用static修饰,常常用来给类进行初始化,在加载的时候就执行,并且只执行一次。
代码块的执行顺序:
静态代码块->main()方法->构造代码块->构造方法->局部代码块
7. 内部类
内部类指的是定义在一个类里面的类。有多种形式,例如:
- 成员内部类:
成员内部类是定义在外部类中的类,地位通常和外部类的成员方法、变量地位类似。
成员内部类可以访问外部类的所有成员,包括私有成员。
成员内部类的实例必须使用外部类的实例来调用。
public class Outer {
private int outerVar;
public Outer(int outerVar) {
this.outerVar = outerVar;
}
class Inner {
public void display() {
System.out.println("OuterVar: " + outerVar);
}
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer(10);
Outer.Inner inner = outer.new Inner();
inner.display(); // 输出:OuterVar: 10
}
}
- 局部内部类:
局部内部类通常定义在方法内部的类,作用域只在方法的内部。
局部内部类可以访问外部类的成员,也可以访问方法内的局部变量。
局部内部类在外界无法直接调用,需要在方法内实例化调用。
public class Outer {
private int outerVar;
public Outer(int outerVar) {
this.outerVar = outerVar;
}
public void display() {
class LocalInner {
public void show() {
System.out.println("OuterVar: " + outerVar);
}
}
LocalInner inner = new LocalInner();
inner.show();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer(10);
outer.display(); // 输出:OuterVar: 10
}
}
- 匿名内部类:
匿名内部类是一个没有类名的内部类,直接实现接口或者继承类,并在实例化时定义类的内容。
匿名内部类通常是用了来创建临时性的、使用一次的类。
public interface Greeting {
void greet();
}
public class Main {
public static void main(String[] args) {
Greeting greeting = new Greeting() {
@Override
public void greet() {
System.out.println("Hello!");
}
};
greeting.greet(); // 输出:Hello!
}
}
- 静态内部类:
静态内部类是定义在外部类中,使用static修饰的内部类。
静态内部类可以通过外部类访问,也可以直接实例化。
public class Outer {
private static int outerVar;
static class StaticInner {
public void display() {
System.out.println("OuterVar: " + outerVar);
}
}
}
public class Main {
public static void main(String[] args) {
Outer.StaticInner inner = new Outer.StaticInner();
inner.display(); // 输出:OuterVar: 0
}
}
- 总的来说:
成员内部类适用于与外部类关系密切的情况下,需要访问外部类的成员。
局部内部类用于方法内部需要一个特定功能的类。
匿名内部类用于临时性、使用一次的类。
静态内部类用于与外部类关系密切,但不需要访问外部类实例的情况。
8. 关键字
关键字在面向对象编程中扮演着重要的角色,它们用于定义类、方法、变量等,并控制其行为。以下是一些关键字在面向对象各个方面的作用:
-
类定义:
class
:用于定义类。extends
:用于表示继承关系,子类继承父类。implements
:用于表示实现接口,类实现接口中定义的方法。abstract
:用于定义抽象类,抽象类不能实例化,可以包含抽象方法。interface
:用于定义接口,接口中包含一组方法的规范,而不包含实现。
-
方法定义:
void
:表示方法没有返回值。public
、protected
、private
、`默认:表示方法的访问权限。static
:表示方法是静态方法,可以通过类名直接调用。final
:表示方法是最终方法,不能被子类重写。abstract
:表示方法是抽象方法,只有方法签名,没有方法体。
-
变量定义:
public
、protected
、private
:表示变量的访问权限。static
:表示静态变量,属于类,所有对象共享。final
:表示常量,不能再被赋值。transient
:表示变量不参与序列化。volatile
:表示变量是易变的,不会被缓存到线程本地内存中。
-
抽象类:
public
:默认使用public作为访问修饰符。private
:表示为私有方法,可以有方法体,并且不重写。
-
接口:
public static final
:成员变量默认修饰符。public abstract
:成员方法默认修饰符。public default
:默认方法,不是抽象方法,不被强制重写,可以在实现类中去掉default关键字选择重写。private
:私有方法,不是抽象方法,不可重写,默认方法可以调用私有的静态方法和非静态方法。static
:声明静态方法。
-
其他:
this
:表示当前对象的引用。super
:表示父类的引用。new
:用于创建对象。instanceof
:用于判断对象是否属于某个类或接口的实例。return
:用于从方法中返回值。
这些关键字在面向对象编程中起着重要的作用,通过合理使用这些关键字,可以定义出结构清晰、功能完善、易于维护的代码。