Java面向对象的三大特征
—————————————————————————————————————————————————————————————————————
1.封装
面向对象的三大特征:封装、继承、多态
封装是面向对象的三大特征之一;
类就是一种封装:类就是把要处理的对象的数据,以及对这些数据进行处理的方法封装成一个架子。
例如:日常中,手机、洗衣机、汽车……等都是封装。
封装的设计要求:合理隐藏,合理暴漏。
哪些需要隐藏,哪些需要暴露,怎么隐藏,怎么暴露?
如何隐藏:使用private关键字修饰成员变量,就只能在本类中访问,其他任何地方不能直接访问。
如何暴露(合理暴露):使用public修饰(公开)的getter和setter方法合理暴露。
2.继承
面向对象的高级语法之一:继承。
什么是继承?为什么要有继承?
定义的javabean实体类中会有大量的重复代码,怎么把重复的代码抽出来,让其他实体类共同使用呢?——即所有人都叫他爸爸。(减少一些重复代码的使用)
Java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系。
public class B extends A{
}
// A类称为父类(基类、超类)
// B类称为子类(派生类)
子类能继承啥?
子类能继承父类的非私有成员(成员变量、成员方法)
继承后对象的创建:
子类的对象是由子类、父类共同完成的
子类对象其实是由子类和父类多张设计图共同创建出来的对象,所以子类对象是完整的。
什么是权限修饰符?
就是用来限制类中的成员(成员变量、成员方法、构造器)能够被访问的范围
权限修饰符:
private:只能本类;
缺省:本类,同一个包中的其他类;
protected:本类,同一个包中的类,子孙类;
public:任意位置。
private
常用情境:
成员变量大概率会private修饰,
成员方法大概率会public修饰,
而缺省和protected一般不会使用。
继承的特点:
单继承,一个类只能继承一个父类,只能有一个爸爸;
多层继承,Java不支持多继承,但支持多层继承,就是爸爸也会继承;
Java中的类为什么不支持多继承?
用反证法,假设A、B中都有个method方法,A是复习语文的,B是复习数学的
假如C可以同时继承 A和B,如果用C创建对象,C可以调用父类的方法,这是C就不知道去调用A还是B的方法。
继承后子类访问成员的特点:就近原则
① 在之类中访问其他成员,是依照就近原则的,先在子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
② 如果子父类中,出现了重名的成员,会优先使用子类的成员。
如果此时一定要在子类中使用父类的怎么办?
可以通过super关键字,指定访问父类的成员:
super.父类成员变量;
super.父类成员方法;
方法重写:
当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
方法重写规范:
① 方法名称,形参列表必须一样,这个方法就是方法重写。
② @Override,方法重写的校验注解(标志):要求方法名称和形参列表必须与被重写方法一致,否则报错!(更安全,可读性更好,更优雅)
③ 子类重写父类的方法时,访问权限必须大于或者等于父类该方法的权限 (public > protected > 缺省)
④ 重写的方法,返回值类型必须与被重写方法的返回值类型一样,或者范围更小。
⑤ 私有方法,静态方法不能被重写,如果重写会报错;
⑥ 重写的规范:声明不变,重新实现。
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.cry();
}
}
class Animal{
public void cry(){
System.out.println("动物会叫~~~ ");
}
public static void cry2(){
System.out.println("----------");
}
}
class Cat extends Animal{ // Cat类继承Animal类
// 重写的规范:声明不变,重新实现。
@Override // 方法重写的校验注解(标志)
public void cry(){ // 方法重写:方法名称,形参列表必须一样
System.out.println("猫 喵喵喵的叫~~~");
}
}
public class Test2 {
public static void main(String[] args) {
Student s = new Student("赵敏",'女',25);
System.out.println(s);
System.out.println(s.toString());
}
}
class Student{
private String name;
private char sex;
private int age;
// 重写的toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", sex=" + sex +
", age=" + age +
'}';
}
// 无参构造器
public Student() {
}
// 有参构造器
public Student(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
子类构造器特点:子类的全部构造器,都会先调用父类的构造器,再执行自己。
子类构造器是如何实现调用父类构造器的?
默认情况下,子类全部构造器的第一行代码都是super()(写不写都有),它会调用父类的无参构造器;
如果父类没有无参构造器,则我们必须在子类构造器的第一行手写super(……),指定去调用父类的有参构造器。
子类构造器可以通过调用父类构造器,把对象中包含父类这部分的数据先初始化赋值,
再回来把对象里包含子类这部分的数据也进行初始化赋值。
public class Test {
public static void main(String[] args) {
Zi zi = new Zi(); // 新建一个子类的对象
}
}
class Zi extends Fu{ // Zi类继承Fu类
public Zi(){
// super(); // 默认存在的,写不写都有
// super(666); // 指定调用Fu类的有参构造器
// 如果父类没有无参构造器,则必须在子类构造器的第一行写这个super(666),指定去调用父类的有参构造器。
System.out.println("子类无参构造器");
}
}
class Fu{
public Fu(){ // 无参构造器,私有的时,不能被子类继承
System.out.println("父类无参构造器");
}
public Fu(int a){ // 有参构造器
System.out.println("父类有参构造器");
}
}
public class Student {
private String name;
private char sex;
private int age;
private String school;
public Student() {
}
public Student(String name, char sex, int age) {
// this调用兄弟构造器
// 注意:super(...) this(...)必须写在构造器的第一行,而且两者不能同时出现
this(name,sex,age,"家里蹲大学"); // 悄悄使用兄弟构造器
// super(); // this下面第二行不能使用super关键字调用父类的构造器
// 当前构造器是传入三个形参变量,但是要求该类在初始化赋值时,
// 如果没有传入school变量,该类能够自动对school变量进行赋值"XXX大学"
// 所以该构造器要手动调用它的兄弟构造器(传入四个形参变量),其中一个手动赋值。
// this.name = name;
// this.sex = sex;
// this.age = age;
// this.school = "黑马程序员";
}
public Student(String name, char sex, int age, String school) {
this.name = name;
this.sex = sex;
this.age = age;
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", sex=" + sex +
", age=" + age +
", school='" + school + ''' +
'}';
}
}
3.多态
Animal a1 = new Wolf(); // 对象多态:父类引用指向子类对象
Animal a2 = new Tortoise();
public class Test1 {
public static void main(String[] args) {
// 1、对象多态
Animal a1 = new Wolf(); // 父类引用指向子类对象
a1.run(); // 方法:编译看左边,运行看右边
// 当Animal中没有run方法时,编译报错
// 当运行run方法时,会去Wolf中找run方法运行
Animal a2 = new Tortoise();
a2.run(); // 方法:编译看左边,运行看右边
// 成员变量:编译看左边,运行也看左边
System.out.println(a1.name); // Animal
System.out.println(a2.name); // Animal
// 使用父类类型数组管理不同子类对象
Animal[] animals = {a1, a2};
for (Animal animal : animals) {
animal.run(); // 动态绑定,运行时确定调用哪个子类的方法
}
}
}
public class Animal {
String name = "Animal";
public void run(){
System.out.println("Animal is running");
}
}
public class Wolf extends Animal{
String name = "狼";
@Override
public void run() {
System.out.println("狼跑的贼溜~~~");
}
}
public class Tortoise extends Animal{
String name = "乌龟";
@Override
public void run() {
System.out.println("乌龟跑的贼慢!!!");
}
}
public class Test2 {
public static void main(String[] args) {
// 1、多态的好处1:右边的对象是解耦合的。
Animal a1 = new Tortoise();
a1.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
// 2、多态的好处2:父类类型的变量作为参数,可以接受一个子类对象
Wolf w = new Wolf();
go(w);
Tortoise t = new Tortoise();
go(t);
}
// 宠物游戏:所有动物都可以送给这个方法开始跑步
public static void go(Animal w) {
System.out.println("开始......");
w.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
}
}
public class Animal {
String name = "Animal";
public void run(){
System.out.println("Animal is running");
}
}
public class Wolf extends Animal {
String name = "狼";
@Override
public void run() {
System.out.println("狼跑的贼溜~~~");
}
// 子类独有的功能:狼吃羊
public void eatSheep() {
System.out.println("狼吃羊");
}
}
public class Tortoise extends Animal {
String name = "乌龟";
@Override
public void run() {
System.out.println("乌龟跑的贼慢!!!");
}
// 子类独有的功能:乌龟缩头
public void shrinkHead() {
System.out.println("乌龟缩头了!!!");
}
}
多态下会产生一个问题,怎么解决?
多态下不能调子类独有的功能——多态下的类型转换
多态下的类型转换:
自动类型转换:父类 变量名 = new 子类();
强制类型转换:子类 变量名 = (子类) 父类变量;
强制类型转换的注意事项:
存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。
运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来;
类型转换异常解决:
强转前:使用instanceof 关键字,判断当前对象的真实类型,再进行强转。
public class Test3 { // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
public static void main(String[] args) {
Animal a1 = new Wolf(); // ===> Animal a1 = new Tortoise();
a1.run();
// 强制类型转换
Tortoise t1 = (Tortoise) a1; // 编译阶段不会报错,但是运行时会报错
// 解决方法:将第一行的new Wolf(); 换成 new Tortoise();
t1.shrinkHead();
System.out.println("============================");
// 有继承关系就可以强制转换,编译阶段不会报错!!
// 运行时可能会出现类型转换异常:ClassCastException
Wolf w = new Wolf();
go(w);
Tortoise t = new Tortoise();
go(t);
}
// 宠物游戏:所有动物都可以送给这个方法开始跑步
public static void go(Animal w) {
System.out.println("开始......");
w.run();
// Java建议:强制类型转换前,应该判断对象的真实类型,再进行强制类型转换
if(w instanceof Wolf){
Wolf w1 = (Wolf) w;
w1.eatSheep();
}else if(w instanceof Tortoise){
Tortoise t11 = (Tortoise) w;
t11.shrinkHead();
}
}
}
4.其他
继承的特点:
祖宗类,Java中所有的类都是Object的子类。
Java中所有的类,要么直接继承了Object,要么默认继承了Object,要么间接继承了Object;因此Object是所有类的祖宗类,Object 功能任何类都可以用。
例如:Object类中的toString方法,会自动返回类的地址。
就近原则,优先访问自己类中,自己类中没有的才会访问父类。
可以把多个类写在一个文件中,但是在开发中不建议这样写;
多个类写在一个文件中,但是只能有一个类用public修饰,就是公开类只能有一个暴露出去
解耦合:遥控器没电了,拔出来换个电池就可以继续使用了。遥控器和电池是解耦合了
注解:
lombook技术可以实现为类自动添加getter 和 setter 方法,无参构造,重写toString 方法;使用@Data注解可以调用lombook技术,自动生成getter 和 setter 方法,无参构造,toString 方法。
@AllArgsConstructor注解提供所有的有参构造器,这个时候无参构造器没了,所以还需要加注解@NoArgsConstructor,提供无参构造器
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// lombook 可以实现为类自动添加getter 和 setter 方法,无参构造,toString 方法
// @Data注解可以自动生成getter 和 setter 方法,无参构造,toString 方法
@Data
@NoArgsConstructor // 提供无参构造器
@AllArgsConstructor // 提供所有的有参构造器
public class Card {
private String carId;
private String name;
private String phoneNumber;
private double money; // 账户余额
}
重载和重写的区别:
① 重载指的是在一个类中定义多个方法名相同但参数列表不同的方法。编译器根据调用时传递的参数类型和数量来决定具体调用哪个方法。重载与返回类型无关,也就是说,仅改变返回类型而不改变参数列表的方法不会被视为重载。
② 重写是指子类重新定义从父类继承而来的方法。重写的方法必须具有相同的名称、参数列表以及返回类型(或者返回类型的协变)。此外,重写方法的访问级别不能比被覆盖的方法更严格,并且重写方法不能抛出新的检查异常或更广泛的异常。
总结区别:
| 特性 | 重载(Overloading) | 重写(Overriding) |
| ---- | ---- | ---- |
| 发生地点 |同一个类内 | 父类与子类之间 |
| 方法签名 |必须不同(参数列表不同) |必须相同(包括参数列表和返回类型) |
| 返回类型 |不影响是否为重载 |必须相同或返回类型的协变 |
| 访问修饰符 |可以不同 |子类方法的访问权限不能比父类方法更严格|
| 异常处理 |没有特殊限制 |子类方法不能抛出新的检查异常或更广泛的异常|
| 决定调用时机 | 编译时 |运行时 |
参与评论
手机查看
返回顶部