文章目录
(一)封装
(1)什么是封装
当我们需要安全地访问对象时,例如限制给对象赋值的范围(避免数据类型的不同或者数据范围超出预计等),就需要使用封装技术。
封装就是将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法(getter和setter)来实现对隐藏信息的操作和访问。
(2)封装的步骤
- 使用关键字private对类的属性进行隐藏(方法一般不用隐藏);
- 利用setter / getter方法对属性值进行操作;
- 可以在方法中加入条件控制语句,进行限制。
(3)封装示例
/**
* 用户实体
* 包括用户编号,姓名,用户名,密码,用户权限等级
*/
public class User {
private int id; //用户id
private String name; //姓名
private String unm; //用户名
private String pwd; //密码
private int level; //用户权限等级
public User() {
}
public User(int id, String name, String unm, String pwd, int level) {
this.id = id;
this.name = name;
this.unm = unm;
this.pwd = pwd;
this.level = level;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUnm() {
return unm;
}
public void setUnm(String unm) {
this.unm = unm;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
(4)this关键字
在使用setter方法的时候,会用到this关键字。这是因为方法的形参和类的属性重名,为了区分而使用的。
属性与局部变量(形参就是一种局部变量)若同名,则属性会被局部变量所覆盖、无法在该方法内起作用。此时,若想在该方法内使用被覆盖的成员变量,必须使用this或类名作为调用者来访问。
(二)继承
(1)概述
继承是面向对象的三大特征之一,可以使得子类具有父类的属性和方法。另外,还可以在子类中重新定义、追加属性和方法。
继承是在原有类的基础上,进行功能扩展,创建新的类型。
(2)注意事项
- Java中的类只有单继承(即一个类只能有一个父类),没有多继承(即一个类不能同时有多个父类);
- Java中的类支持多层继承(即允许A继承B、B继承C……);
- 继承是类和类之间的一种关系。此外,类和类之间的关系还有依赖、组合、聚合等;
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类 / 超类)。子类继承父类,使用关键字
extends
来表示。- 子类和父类之间,从意义上讲应该具有
子类 is a 父类
的关系; extends
的意思是“扩展”,子类是父类的扩展。
- 子类和父类之间,从意义上讲应该具有
- 当调用子类中的一个成员变量 / 成员方法时,最先在子类中寻找,如果没找到就再去父类中寻找,如果都没有找到就报错。(即,不会再去父类的父类中寻找——这点也很好理解,如果父类的父类中有的话,父类会继承过来、不可能找不到)
(3)语法
继承的语法格式:
public class 子类名 extends 父类名 {
//...
}
例子:
//父类
public class Person {
public int money = 1000;
public void say() {
System.out.println("说...");
}
}
//子类
public class Student extends Person {
}
//执行
public class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.say();
System.out.println(stu.money);
}
}
可见,虽然在子类Student中并没有定义属性、方法,但由于Student类继承了父类Person,因此Student类中自然就含有了父类当中的属性、方法,可以当成自己的属性、方法一样去使用。
(4)访问修饰符
Java访问修饰符是用来控制类、方法和变量的访问权限。
Java中一共有4种访问修饰符,分别是:
- public:公共访问修饰符;
- protected:保护访问修饰符;
- default:默认访问修饰符(也就是啥也不写);
- private:私有访问修饰符。
1.public
范围:不同类、不同包下都可以任意访问。
2.protected
范围:①本类内部;②子类内部;③与继承关系之中的父类处于同一个包中的其他类。
例如,A是父类(A中定义了一个protected的属性),B是子类,B类继承A类,那么,
- 在A类自己的类内部,可以随意访问该protected属性;
- 在子类B自己的内部,可以随机访问该protected属性;
- 有另一个C类,想通过new实例化对象的方式对该protected进行访问,那么它需要new一个A类,或者new一个B类。但此时,C类如果与A类在同一个包下,则能够通过
对象.属性
的方式访问到;反之,若C类与A类不在同一个包下,则访问不到该protected属性。
3.default
当定义变量、方法及类时,若不写访问修饰符,我们称之为默认的访问修饰符,用一个词来形容就叫default。
范围:①本类内部;②同一个包下的其他类。——即,专门针对本包访问而设计。
注意:在接口中,默认的访问修饰符是public(即,你不写访问修饰符时,它会自动认为是public)。
4.private
范围:只能在当前类内部访问,其他地方一律访问不到(无论是同包、跨包,无论是不是子类)。
(5)super关键字
super关键字的用法和this关键字的用法类似。
- this:代表对本类对象的引用;
- super:代表对父类对象的引用。
关键字 | 访问成员变量 | 访问构造方法 | 访问成员方法 |
---|---|---|---|
this | this.成员变量 | this([参数列表]) | this.成员方法([参数列表]) |
super | super.成员变量 | super([参数列表]) | super.成员方法([参数列表]) |
(6)方法重写(Override)
1.概念
子类中出现了和父类中一模一样的方法声明。
2.应用场景
当子类需要父类的功能,而在此基础上子类又有自己特有的内容时,可以重写父类中的方法。这样,既沿袭了父类的功能,又定义了子类特有的内容。
3.注解
@Override
是一个注解,可以帮助我们检查重写方法的方法声明的正确性。
4.注意事项
- 子类重写父类的方法,重写后的方法的访问权限不能比父类的更低。(即子类重写的方法不能比父类具有更严格的访问权限)
- 子类重写父类的方法,必须是一模一样的方法声明。即,
- 方法名不能修改;
- 参数列表不能修改;
- 返回值,如果是基本数据类型,则不能修改;如果是引用数据类型,则可以是原本返回值的子类(这一点涉及到多态的知识点)。
5.重写与重载的区别
重写(Override)一定是发生在继承关系中,至少伴随着2个类,是子类把父类中具有相同声明(方法名相同、参数列表相同、返回值相同或为子类)的方法重新写一遍。
重载(Overload)一定是发生在同一个类下的,与返回值无关,方法名不能修改,且参数列表必须不一样(形参个数不一样、形参类型不一样、形参顺序不一样)。
注意:
子类继承父类后,在子类中可以对从父类继承过来的方法进行重载。这样一来,两个方法看似是处于两个类中,似乎与“重载一定发生在同一个类下”相矛盾。实际上并不矛盾,子类继承了父类,实际上父类的那个方法已经隐式地存在于子类当中了,因此仍然是在同一个类下进行方法重载。
(7)继承中的构造方法
父类的构造函数不能被子类继承。
子类中所有的构造方法,默认都会先访问父类的无参构造方法。——即,在子类的每一个构造方法中,都会隐式地默认在开头处执行一行super();
很好理解,由于子类要继承父类中的数据,所以,父类必须先初始化、开辟堆空间,否则子类去哪访问、操作父类的属性或方法呢?
至此,再结合“构造函数”本身的相关知识点,我们可以分析出以下几个特殊情况的原理:
1.如果在父类中手动进行了含参构造函数的定义,则此时系统默认提供的无参构造函数就不存在了,从而会导致子类的构造方法报错(因为子类调用不到父类的无参构造函数了,从而无法使父类初始化)。分析其原理,可知两种解决办法:①手动再给父类定义一个无参构造函数(推荐);②使用父类中定义的含参构造函数对父类进行初始化。即,在子类构造方法的第一行处,手动调用父类的含参构造函数super(形参列表);
2.会出现一个现象,我们在实例化一个子类对象时,子类的构造函数会自动执行。但与此同时,父类的构造函数也执行了,且在子类的之前。例子如下:
class Animal {
public Animal() {
System.out.println("I am Animal.");
}
}
class Dog {
public Dog() {
System.out.println("I am Dog.")
}
}
此时,如果实例化对象,执行Dog d = new Dog();
,则运行结果如下:
I am Animal.
I am Dog.
(三)多态
(1)概念
简单地说就是一个抽象的对象有多种具体的形态。
具体来说就是父类中的方法被子类继承后,可以通过子类重写,使之在各个子类中具备不同的含义和作用。
举例:
class Animal {
public void eat() {
System.out.println("吃东西");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("吃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("吃鱼");
}
}
上述代码中,父类中同一个eat()
方法在不同子类中表现出不同的行为,就是多态。
(2)条件
三大条件:
- 继承:必须存在继承;
- 重写:子类必须对父类中的方法进行重写;
- 向上转型:父类引用指向子类对象。
(3)向上转型与向下转型
1.向上转型
向上转型(upcasting)是指将一个子类的对象引用赋值给其父类类型的引用变量。这是在面向对象编程中的一种常见操作,用于实现多态性的灵活的对象处理。
在向上转型中,子类对象可以被视为父类对象(就像byte b = 1; int i = b;
一样,b就被视为int、按照int来继续进行后续操作了),可以使用父类类型的引用变量来引用子类对象。这样做的好处是可以以统一的方式处理不同类型的对象,实现代码的灵活性和可扩展性。
特点:
- 子类对象可以隐式地转型为父类对象,不需要任何显式的类型转换操作;
- 父类引用变量可以引用子类对象,但通过父类引用变量只能访问到子类对象中定义的父类成员,无法访问子类独有的成员。如果确实要访问,有两种方法:
- 向下转型转回去。如:
Animal ani = new Dog();
,再向下转型Dog dog = (Dog) ani;
- 不向上转型,直接自己new自己。如:
Dog dog = new Dog();
- 向下转型转回去。如:
- 子类对象中重写的方法,在通过父类引用变量调用时,会调用子类中的实现。
- 向上转型是安全的操作,因为子类对象本身就是一个父类对象。
举例:
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog(); //父类的引用指向子类的对象
Animal cat = new Cat(); //父类的引用指向子类的对象
dog.eat();
cat.eat();
}
}
2.向下转型
向下转型(downcasting)是指将一个父类类型的引用变量转换为其子类类型的引用变量。它与向上转型相反,需要进行显式的类型转换操作。
在某些情况下,当一个对象被向上转型后,它的具体类型信息会丢失,只保留了父类类型的信息。如果我们需要访问子类中特有的成员,就需要使用向下转型。
需要注意的是,向下转型是有风险的,因为转换的对象必须是实际上的子类对象才能成功,否则会在运行时抛出ClassCastException
异常。
举例:
class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating.")
}
//子类独有的方法
public void bark() {
System.out.println("Dog is barking.")
}
}
public class Main {
public static void main(String[] args) {
Animal ani = new Dog(); //向上转型
//使用向下转型之前,需要先检查对象是否实际上是子类的实例
//错误示例:
//从语法上是对的,符合强制类型转换。但一运行就会报错(ClassCastException),因为类型不匹配。
//Cat cat = (Cat) ani;
if(ani instanceof Dog) {
Dog dog = (Dog) ani; //向下转型
dog.bark();
} else {
System.out.println("ani is not an instance of Dog.")
}
}
}
说明:
instance
运算符用来检查ani
是否是Dog
类的实例,如果是,返回true,否则返回false,以此确保向下转型时的类型安全。