三大特性
什么是面向对象的编程语言?为什么java是面向对象的编程语言?
面向对象的语言有三大特征:封装、继承、多态,因为Java有这三大特性。
封装
封装的作用?
封装可以隐藏对象的内部实现细节,控制对象的修改与访问权限。
属性封装
属性封装可控制外部对属性的访问,属性封装后我们需要对其属性编写对应的公有方法get和set,让外部通过公有方法访问属性。
属性的封装可以让我们在对应的公有方法中控制外部读取的数据和设置数据的有效性。
属性封装格式为:private 数据类型 属性;
private:修饰符,可以将属性定义为私有属性,外部类无法访问,只能通过我们设置的公有方法get和set对属性进行操作;
例如:当我们有一个Person类,且我们创建该类的对象,并且给他赋值,如下:
public class Demo1 { public static void main(String[] args) { //创建一个Person对象p1 Person p1 = new Person(); //设置p1的属性值 p1.name = "张三"; p1.age = 200; p1.Hobby = "跑步"; //姓名为:张三,年龄200,爱好是跑步 System.out.println("姓名为:"+p1.name+",年龄"+p1.age+",爱好是"+p1.Hobby); } } public class Person { //给类定义三个属性(姓名,年龄,爱好) String name; int age; String Hobby; }问题:此时我们设置的年龄是否符合实际生活?
因此我们需要设置数据的有效性,控制用户能够设置的大小,此时我们需要用到封装技术,修改代码如下:
public class Demo1 { public static void main(String[] args) { //创建一个Person对象p1 Person p1 = new Person(); //此时我们需要通过公有方法访问属性 p1.setName("张三"); //此时用户设置的年龄若不符合,则会返回默认值18 p1.setAge(50); p1.setHobby("跑步"); //我们需要使用公有方法才能访问0 System.out.println("姓名为:"+p1.getName()+",年龄"+p1.getAge()+",爱好是"+p1.getHobby()); } } public class Person { //给类定义三个属性(姓名,年龄,爱好) private String name; private int age; private String hobby; //为属性设置其公有方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { //控制用户输入的值,不符合则设置一个默认值 if (this.age >= 0 && this.age <= 150) { this.age = age; }else{ this.age=18; } } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } }
封装的好处:在项目中我们需要对所有属性进行封装,并编写对应公有方法,这样可以增加项目的可扩展性和可维护性。
方法封装
方法封装好处:当有些方法不需要外部调用时,我们可以对方法进行封装,设置为私有方法,需要被外部调用的方法就设置为公有方法。
方法封装格式为:private 返回值类型 方法名(){}
例如:当我们有Animal类,为该类编写一个run方法和一个stop方法,run方法中调用stop方法表示动物跑一段时间自己停下来,我们为其Animal类创建一个Dog对象,调用其run方法,那么我们可以将stop设置为私有方法,因为stop方法不会被外部调用。
继承
在生活中的继承例如子承父业,儿子继承父亲的财产等等。那么程序中的继承会一样吗?
继承的概念
在程序中,继承是类继承,分为子类和父类,即当A继承B,那么A就是子类,B就是父类;子类是可以继承父类的所有属性与方法。当我们的一些类有共同属性或方法时我们会让这些类继承同一个大类,在这个大类中设置他们的共同属性和方法。
继承的格式:public class 子类名 extends 父类名{}
例如:当我们有Animal、Dog、cat三个类,Animal类中定义了一些共同属性和方法,例如属性type(种类)、name(名字),方法为eat(吃),当我们让Dog和cat类继承Animal类且在这两个类中不写代码时,为Dog类创建一个对象,那么这个对象能否使用eat方法和设置属性呢?
代码如下:
public class Demo1 { public static void main(String[] args) { Dog d1 = new Dog(); Cat c1 = new Cat(); d1.type ="狗"; d1.name = "小白"; d1.eat(); c1.type = "猫"; c1.name = "小黑"; c1.eat(); //种类为狗的动物,姓名为小白,正在吃东西 //种类为猫的动物,姓名为小黑,正在吃东西 } } public class Animal { String name; String type; public void eat(){ System.out.println("种类为"+this.type+"的动物,姓名为"+this.name+",正在吃东西"); } } public class Dog extends Animal{ } public class Cat extends Animal{ }
继承的好处:它可以提高语句的复用性和可扩展性,减少代码冗余(将共同属性或方法写入同一个父类)。
Java属于单继承,每一个类只能有一个父类,但是可以间接继承,属性和方法会逐级叠加。(IDEA可以使用CTRL + H查看类的关系树)。
注:Java中所有类都默认继承Object类(Java中万物皆对象),Object类属于顶级父类。
例如: 有三个类A、B、C,A继承B,B继承C,那么A与C的关系就属于间接继承,A既可以使用B的属性方法,也可以使用C的。且你们调用方法时,他们三个都有一个顶级父类为Object。
不可继承
构造方法只负责创建本类对象,视作不可继承;
private修饰的属性或者方法,只能本类可见,子类无法直接访问,视作不可继承;
当访问没有修饰符的属性和方法时,修饰符为默认default,只能在同一包中访问,若子类不在同一包中,无法直接访问,视作不可继承。
注意:这里的视作不可继承意思为,在继承时父类的所有方法和属性都被继承到子类,但是由于这些 原因,子类无法直接使用。
例如:当父类Person修饰的属性name,子类继承时无法直接使用name属性,但是可以通过公有方法设置属性值,说明底层中这些方法和属性继承过来了,但是因为修饰符或者其他原因,在子类无法直接使用。
访问修饰符
访问修饰符范围(从小到大):private-->default-->protected-->public
本类 | 同包不同类 | 非同包子类 | 其他 | |
---|---|---|---|---|
private | √ | × | × | × |
不写修饰符(default) | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | × |
方法重写
当子类中定义了一个与父类完全相同的方法时,会覆盖父类方法,称为方法重写。(override)
public class Demo1 { public static void main(String[] args) { Dog d1 = new Dog(); d1.eat(); //狗再吃... } } public class Animal { public void eat(){ System.out.println("动物再吃..."); } } public class Dog extends Animal{ public void eat(){ System.out.printf("狗再吃..."); } }
那么什么地方会用到方法重写呢?
当父类方法无法满足子类需求时,我们可以在子类重写父类的该方法。
例如:在英雄联盟这款游戏中,我们可以创建一个父类为野怪类,定义一个攻击方法,再创建一个小龙、峡谷先锋类并且继承父类野怪类。小龙、峡谷先锋攻击的方式都不同,有的可以远程攻击、有的近身攻击,那么我们就需要在这两个类中重写父类中的攻击方法。此时如果我们再创建一个大龙类,那么大龙类的攻击也可以继续重写父类的这个方法。
语法要求:
方法名称、参数列表、返回值类型、异常声明必须与父类相同;
访问修饰符可比父类更广;
可以再被重写的方法前面添加@Override判断是否为重写父类方法,不是就报错,以免重写方法名错误。
@Override public void rent() { super.rent(); }
super关键字
表示父类对象,调用父类属性和方法。
注意:
子类在继承父类后,子类构造方法会默认自动调用父类的无参构造方法;
当父类没有无参构造方法时,子类就会报错。
在父类中声明一个无参构造方法
在子类构造方法中,手动调用父类中的有参构造方法,
在子类构造方法中调用父类构造方法时,必须将放在第一行。
public class Demo1 { public static void main(String[] args) { Dog d1 = new Dog(); //Animal构造方法... //Dog无参构造方法 } } public class Animal { public Animal(){ System.out.println("Animal构造方法..."); } } public class Dog extends Animal{ public Dog(){ super();//当不写这句时,他也会自动先调用父类构造方法 System.out.println("Dog无参构造方法"); } }
面试题
this与super的异同?
相同点:
都可以调用属性和方法;
都可以调用无参构造方法
调用构造方法时需放在第一行
不同点:
super调用父类的属性和方法、构造方法;
this调用本类的属性和方法、构造方法;
注意:构造方法中不能同时出现super和this,一般出现this就不能再写super,将super写入this调用的构造方法第一行即可。
继承中对象调用创建的过程
在创建子类对象时,会先创建父类对象。
流程:先分配父类+子类的空间,先创建父类对象,并初始化父类属性,调用父类的构造方法,再创建子类对象,并初始化子类属性,再调用子类构造方法
由上可知:为什么子类构造方法会自动调用父类无参构造方法。
多态
生活中的多态是什么样的?
例如:1.你在你的家人中的身份是多态的,你的父亲喊你儿子,你的爷爷喊你孙子,你的老师喊你为学生,在你同学眼里你是它的同学。一个对象在不同人眼里有多种身份。
2.一个哈士奇,一个懂狗的人看到就知道这只狗是哈士奇,但是在不懂狗的人口中就是一只狗。
程序中的多态
用父类引用类型指向子类对象时,会呈现多种状态,称之为多态。
注意:
-
当用父类引用类型指向子类对象时,就只能使用父类属性和方法,子类独有的属性无法直接使用。
-
不能用子类引用指向父类
当在子类中重写了父类方法,即使是通过父类引用来调用该方法,也会执行子类重写后的方法,毕竟是子类对象,而该对象已经覆盖了父类的方法。
public class Demo1 { public static void main(String[] args) { Animal apper = new Dog(); apper.eat(); //狗再吃.... } } public class Animal { public void eat(){ System.out.println("动物再吃..."); } } public class Dog extends Animal{ public void eat(){ System.out.println("狗再吃...."); } }
多态应用场景:
-
场景一:使用父类作为方法参数实现多态,使方法参数的类型更为广泛;
-
例如:主人喂养动物案例
-
-
场景二:使用父类作为方法返回值实现多态,使方法可以返回不同子类对象;
-
例如:主人带动物出门案例
-
装箱与拆箱
父类引用中保存真实子类对象,称为向上转型;
Animal apper = new Dog();父类引用中保存真实子类对象,强转回子类本身类型,称为向下转型;
Animal apper = new Dog(); Dog d1 = (Dog) apper;
类型转换异常
当拆箱使用类型不当时,会出现类型转换异常。ClassCastException
Animal apper = new Dog(); Cat d1 = (Cat) apper;//类型转换异常,Dog不能转换为Cat,会报错
Instanceof关键字
instanceof作用:在向下转型前,我们可以通过该关键字判断对象类型正确性,返回Boolean。
格式: 对象 instanceof 类
意义:判断对象与我们是否属于这个类或者这个类的子类创建的对象。
instanceof 是 Java 中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回 true;否则,返回 false。