java-面向对象三大特征
面向对象编程的三大特征:继承、封装、多态
继承
继承是面向对象编程的三大特征之一。继承让我们更加容易实现类的扩展。实现代码的重用,不用再重新发明轮子(don’t reinvent wheels)。
1. 代码复用,更加容易实现类的扩展
2. 方便建模
继承通过extends实现
格式:class 子类 extends 父类 { }
继承的实现
从英文字面意思理解,extends 的意思是“扩展”。子类是父类的扩展。
使用 extends 实现继承
public class Test {
public static void main(String[] args) {
Student s = new Student("高淇", 176, "Java");
s.rest();
s.study();
}
}
class Person {
String name;
int height;
public void rest() {
System.out.println("休息一会!");
}
}
class Student extends Person { // Student继承person类,为其子类,会自动拥有父类的非私有方法及属性
String major; // 专业
public void study() {
System.out.println("在家,自学Java");
}
public Student(String name, int height, String major) { // 子类有参构造,也拥有父类属性
// 天然拥有父类的属性
this.name = name;
this.height = height;
this.major = major;
}
}
继承的好处和弊端😀😅
继承好处
提高了代码的复用性(多个类相同的成员可以放到同一个类中)
提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
继承弊端
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
继承的应用场景:
使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承
is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类
Java中继承的特点
Java中继承的特点
1. Java中类只支持单继承,不支持多继承
错误范例:class A extends B, C { }
2. Java中类支持多层继承
多层继承示例代码:
public class Granddad {
public void drink() {
System.out.println("爷爷爱喝酒");
}
}
public class Father extends Granddad {
public void smoke() {
System.out.println("爸爸爱抽烟");
}
}
public class Mother {
public void dance() {
System.out.println("妈妈爱跳舞");
}
}
public class Son extends Father {
// 此时,Son类中就同时拥有drink方法以及smoke方法
}
继承中变量的访问特点
在子类方法中访问一个变量,采用的是就近原则。
1. 子类局部范围找
2. 子类成员范围找
3. 父类成员范围找
4. 如果都没有就报错(不考虑父亲的父亲…
class Fu {
int num = 10;
}
class Zi {
int num = 20;
public void show() {
int num = 30;
System.out.println(num);
}
}
public class Demo1 {
public static void main(String[] args) {
Zi z = new Zi();
z.show(); // 输出show方法中的局部变量30
}
}
继承使用要点
1. 父类也称作超类、基类。 子类:派生类等。
2. Java 中只有单继承,没有像 C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
3. Java 中类没有多继承,接口有多继承。
4. 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
5. 如果定义一个类时,没有调用 extends,则它的父类是:java.lang.Object。
super方法
this&super关键字:
this:代表本类对象的引用
super:代表父类存储空间的标识(可以理解为父类对象引用)
this和super的使用分别
成员变量:
this.成员变量 - 访问本类成员变量
super.成员变量 - 访问父类成员变量
成员方法:
this.成员方法 - 访问本类成员方法
super.成员方法 - 访问父类成员方法
构造方法:
this(…) - 访问本类构造方法
super(…) - 访问父类构造方法
注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 子类通过this去调用本类的其他构造方法,本类其他构造方法再通过super去手动调用父类的带参的构造方法
注意: this(…)super(…) 必须放在构造方法的第一行有效语句,并且二者不能共存
public class TestSuper01 {
public static void main(String[] args) {
new ChildClass().f();
}
}
class FatherClass {
public int value;
public void f() {
value = 100;
System.out.println("FatherClass.value=" + value);
}
}
class ChildClass extends FatherClass {
public int value;
public int age;
public void f() {
super.f(); // 调用父类的普通方法
value = 200;
System.out.println("ChildClass.value=" + value);
System.out.println(value);
System.out.println(super.value); // 调用父类的成员变量
}
public void f2() {
System.out.println(age);
}
}
构造方法调用顺序:
构造方法第一句总是:super(…)来调用父类对应的构造方法。所以,流程就是:先向上追溯到 Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。
注:静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。
继承中成员方法的访问特点
通过子类对象访问一个方法
1. 子类成员范围找
2. 父类成员范围找
3. 如果都没有就报错(不考虑父亲的父亲…)
super内存图
方法重写 override
1、方法重写概念
子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
2、方法重写的应用场景
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
3、Override注解
用来检测当前的方法,是否是重写的方法,起到【校验】的作用
子类重写父类的方法,可以用自身行为替换父类行为。重写是实现多态的必要条件。
/**
* 测试方法的重写
*/
public class TestOverride {
public static void main(String[] args) {
Horse h = new Horse();
Plane p = new Plane();
h.run();
h.getVehicle();
p.run();
}
}
class Vehicle { // 交通工具类
public void run() {
System.out.println("跑....");
}
public Vehicle getVehicle() {
System.out.println("给你一个交通工具!");
return null;
}
}
class Horse extends Vehicle { // 马也是交通工具
@Override
public void run() {
System.out.println("得得得....");
}
@Override
public Horse getVehicle() {
return new Horse();
}
}
class Plane extends Vehicle {
@Override
public void run() {
System.out.println("天上飞....");
}
}
public class Fu {
private void show() {
System.out.println("Fu中show()方法被调用");
}
void method() {
System.out.println("Fu中method()方法被调用");
}
}
public class Zi extends Fu {
/* 编译【出错】,子类不能重写父类私有的方法 */
@Override
private void show() {
System.out.println("Zi中show()方法被调用");
}
/* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
private void method() {
System.out.println("Zi中method()方法被调用");
}
/* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
public void method() {
System.out.println("Zi中method()方法被调用");
}
}
权限修饰符
关于 protected 的两个细节:
1. 若父类和子类在同一个包中,子类可访问父类的 protected 成员,也可访问父类对象的protected 成员。
2. 若子类和父类不在同一个包中,子类可访问父类的 protected 成员,不能访问父类对象的 protected 成员。
final 关键字
final 关键字的作用:
修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
final int MAX_SPEED = 120;
修饰方法:该方法不可被子类重写。但是可以被重载!
final void study(){}
修饰类: 修饰的类不能被继承。比如:Math、String 等。
final class A {}
继承和组合
除了继承,“组合”也能实现代码的复用!“组合”核心是“将父类对象作为子类的属性”。
public class demo1 {
public static void main(String[] args) {
Student s = new Student("张三", 172, "Java");
s.person.rest(); // s.rest();
s.study();
}
}
class Person {
String name;
int height;
public void rest() {
System.out.println("休息一会!");
}
}
class Student /* extends Person */ {
Person person = new Person(); // 实例化person类,将person类中的各种公共属性和方法变成student的局部变量
String major; // 专业
public Student(String name, int height, String major) {
// 拥有父类的对象,通过这个对象间接拥有它的属性和方法
this.person.name = name; // this.name = name; 这是person类中的属性
this.person.height = height; // this.height = height; 这是person类中的属性
this.person.rest();
this.major = major;
}
}
组合比较灵活。继承只能有一个父类,但是组合可以有多个属性。
比如:上面的例子,Student is a Person 这个逻辑没问题,但是:Student has a Person就有问题了。这时候,显然继承关系比较合适。
Object 类详解
Object 类基本特性
1. Object 类是所有类的父类,所有的 Java 对象都拥有 Object 类的属性和方法。
2. 如果在类的声明中未使用 extends,则默认继承 Object 类。
toString 方法
Object 类中定义有 public String toString()方法,其返回值是 String 类型。Object类中 toString 方法的源码为:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
根据如上源码得知,默认会返回“类名+@+16 进制的 hashcode”。在打印输出或者用字符串连接对象时,会自动调用该对象的 toString()方法。
重写 toString()方法
class Person {
String name;
int age;
@Override
public String toString() {
return name + ",年龄:" + age;
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person();
p.age = 20;
p.name = "张三";
System.out.println("info:" + p);
Test t = new Test();
System.out.println(t);
}
}
==和 equals 方法
“==”代表比较双方是否相同。如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
equals() 提供定义“对象内容相等”的逻辑。比如,我们在公安系统中认为 id 相同的人就是同一个人、学籍系统中认为学号相同的人就是同一个人。
equals()默认是比较两个对象的 hashcode。但可以根据自己的要求重写 equals 方法。
public class TestEquals {
public static void main(String[] args) {
Person p1 = new Person(123, "张三");
Person p2 = new Person(123, "李四");
System.out.println(p1 == p2); // false,不是同一个对象
System.out.println(p1.equals(p2)); // true,id相同则认为两个对象内容相同
String s1 = new String("阿里巴巴");
String s2 = new String("阿里巴巴");
System.out.println(s1 == s2); // false, 两个字符串不是同一个对象
System.out.println(s1.equals(s2)); // true, 两个字符串内容相同
}
}
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
} else {
if (obj instanceof Person) {
Person c = (Person) obj;
if (c.id == this.id) {
return true;
}
}
}
return false;
}
}
JDK 提供的一些类,如 String、Date、包装类等,重写了 Object 的 equals 方法,调用这些类的 equals 方法, x.equals (y) ,当 x 和 y 所引用的对象是同一类对象且属性内容相等时(并不一定是相同对象)返回 true 否则返回 false。
封装
private关键字
概述 : private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
特点 : 被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类使用, 提供相应的操作
提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
编程中封装的具体优点:
提高代码的安全性。
提高代码的复用性。
高内聚:封装细节,便于修改内部代码,提高可维护性。
低耦合:简化外部调用,便于调用者使用,便于扩展和协作。
开发中封装的简单规则:
属性一般使用 private 访问权限。
属性私有后, 提供相应的 get/set 方法来访问相关属性,这些方法通常是public 修饰的,以提供对属性的赋值与读取操作(注意:boolean 变量的 get方法是 is 开头!)。
方法:一些只用于本类的辅助性方法可以用 private 修饰,希望其他类调用的方法用 public 修饰。
JavaBean 的封装演示
public class Person {
// 属性一般使用 private 修饰
private String name;
private int age;
private boolean flag;
// 为属性提供 public 修饰的 set/get 方法
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 boolean isFlag() {// 注意:boolean 类型的属性 get 方法是 is 开头的
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
// 封装的使用
class Person {
private String name; // 私有属性,只能本类访问
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
// this.age = age;//构造方法中不能直接赋值,应该调用 setAge 方法 因为是私有属性,只有调用set方法来设置值;
setAge(age);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
// 在赋值之前先判断年龄是否合法
if (age > 130 || age < 0) {
this.age = 18;// 不合法赋默认值 18
} else {
this.age = age;// 合法才能赋值给属性 age
}
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
public class Test2 {
public static void main(String[] args) {
Person p1 = new Person();
// p1.name = "小红"; //编译错误
// p1.age = -45; //编译错误
p1.setName("小红");
p1.setAge(-45);
System.out.println(p1);
Person p2 = new Person("小白", 300);
System.out.println(p2);
}
}
this关键字
概述 : this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
标准类的代码编写和使用
/*
JavaBean类: 封装数据
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 void show() {
System.out.println(name + "..." + age);
}
}
public class TestStudent {
public static void main(String[] args) {
// 1. 无参数构造方法创建对象, 通过setXxx方法给成员变量进行赋值
Student stu1 = new Student();
stu1.setName("张三");
stu1.setAge(23);
stu1.show();
// 2. 通过带参数构造方法, 直接给属性进行赋值
Student stu2 = new Student("李四", 24);
stu2.show();
}
}
多态(polymorphism)
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。
多态的要点:
1. 多态是方法的多态,不是属性的多态(多态与属性无关)。
2. 多态的存在要有 3 个必要条件:继承,方法重写,父类引用指向子类对象。
3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
多态和类型转换
class Animal {
public void shout() {
System.out.println("叫了一声!");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("旺旺旺!");
}
public void seeDoor() {
System.out.println("看门中....");
}
}
class Cat extends Animal {
public void shout() {
System.out.println("喵喵喵喵!");
}
}
public class TestPolym {
public static void main(String[] args) {
Animal a1 = new Cat(); // 向上可以自动转型
// 传的具体是哪一个类就调用哪一个类的方法。大大提高了程序的可扩展性。
animalCry(a1);
Animal a2 = new Dog();
animalCry(a2);// a2 为编译类型,Dog 对象才是运行时类型。
/*
* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
* 否则通不过编译器的检查。
*/
Dog dog = (Dog) a2;// 向下需要强制类型转换
dog.seeDoor();
}
// 有了多态,只需要让增加的这个类继承 Animal 类就可以了。
static void animalCry(Animal a) {
a.shout();
}
/*
* 如果没有多态,我们这里需要写很多重载的方法。
* 每增加一种动物,就需要重载一种动物的喊叫方法。非常麻烦。
* static void animalCry(Dog d) {
* d.shout();
* }
* static void animalCry(Cat c) {
* c.shout();
* }
*/
}
我们可以看出多态的主要优势是提高了代码的可扩展性。但是多态也有弊端,就是无法调用子类特有的功能,比如,我不能使用父类的引用变量调用 Dog 类特有的seeDoor()方法。
对象的转型(casting)
1. 父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
转型的意思是:如把float类型转成int类型,把double类型转成float类型,把long类型转成int类型;,这些都叫转型。把一种形式转成另外一种形式就叫转型
2. 向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。这时,我们就需要进行类型的强制转换,我们称之为向下转型。
子类的对象可以当作基类的对象来使用称作 向上转型(upcasting)[即父类对象引用指向子类对象],反之称为向下转型(downcasting)
对象的转型
public class TestCasting {
public static void main(String[] args) {
Object obj = new String("祖宗object"); // 向上可以自动转型
// obj.charAt(0) 无法调用。编译器认为 obj 是 Object 类型而不是 String 类型
/*
* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。
* 不然通不过编译器的检查。
*/
String str = (String) obj; // 向下转型
System.out.println(str.charAt(0)); // 位于 0 索引位置的字符
System.out.println(obj == str); // true.他们俩运行时是同一个对象
}
}
实就是多态写法,相当于把子类当作父类来使用。格式:父类 对象 = new 子类()。
向上转型一定是安全的,因为是从小范围转向大范围,相当于猫一定是动物,但动物不一定是猫。
本文来自博客园,作者:link-零,转载请注明原文链接:https://www.cnblogs.com/e-link/p/16706003.html❤❤❤
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)