一、封装
1.什么是封装?
封装是指把一个对象的属性隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
2.为什么要使用封装?
public class Student {
public String name;
public Integer Id;
public Student() {
}
public Student(String name, Integer id) {
this.name = name;
Id = id;
}
}
现有一个Student类,其中的所有属性都是公开的,那也就意味着我们可以随意访问或修改类中的属性值
public class Test1 {
public static void main(String[] args) {
Student student = new Student("Tome",1);
System.out.println(student.Id);
student.Id = -20;
System.out.println(student.Id);
}
}
这些属性值不是说不能修改,即使一定要修改,也要在合适的规则下修改,上面代码中将学生Id设置为负数,很明显是不符合逻辑的。
要解决这个问题,首先需要将属性变为私有,不允许外部直接访问,然后提供对应的set,get方法允许外界操作属性。就像这样:
public class Student {
private String name;
private Integer Id;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return Id;
}
public void setId(Integer id) {
if (id>0){
Id = id;
}else {
System.out.println("输入的Id不合法,请重新输入");
}
}
}
外界想要访问属性值就需要这样:
public class Test1 {
public static void main(String[] args) {
Student student = new Student();
student.setName("Tom");
student.setId(3);
System.out.println(student.getName() + "***" + student.getId());
//如果直接访问name或Id就会报错
// student.name="Jack";
// student.Id=5;
}
}
3.封装的好处
首先,它提高了代码的安全性,它在属性被访问前增加了一个屏障,使得使用者需要在合理的范围内访问和修改属性值,而不是随意修改;其次,它还增强了代码的复用性,今天我封装了一个学生类,以后创建学生对象时都可以使用这个类,无需再次编写学生类的代码。
二、继承
1.什么是继承?
不同类型的对象,相互间经常有一定数量的共同点。比如不同职业的人,他们都属于人类;不同种类的动物,他们都属于动物类;不同品牌的衣服,它们都属于服装类。将这些不同对象中的共有属性和共有方法抽取出来,就可以组成他们的父类。
2.为什么要使用继承?
public class Phone {
private String brandName;
private Integer price;
public Phone() {
}
public void call(){
System.out.println("打电话");
}
public void text(){
System.out.println("发短信");
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
public class HuaWei extends Phone {
private String version;
public HuaWei() {
super();
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
@Override
public void call() {
System.out.println(super.getPrice() + "的" + super.getBrandName() + this.version + "打电话");
}
@Override
public void text() {
System.out.println(super.getPrice() + "的" + super.getBrandName() + this.version + "发短信");
}
}
public class iPhone extends Phone {
private String version;
public iPhone() {
super();
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
@Override
public void call() {
System.out.println(super.getPrice() + "的" + super.getBrandName() + this.version + "打电话");
}
@Override
public void text() {
System.out.println(super.getPrice() + "的" + super.getBrandName() + this.version + "发短信");
}
}
上面的代码中,手机类定义了品牌名称和价格两个属性及其set和get方法,所以它的两个子类就无需重复定义这两个属性了及其方法了,当然,子类也可以有自己的特有属性和方法,比如上面的version属性及其set,get方法。
另外,手机类中还定义了call和text方法,如果这些父类自定义的方法可以满足子类的功能需求,那么子类能拿过来直接用,如果不能的话,子类可以重写父类的方法。
通过以上代码还可以看出super关键字指向父类存储空间,this关键字指向子类存储空间。子类想调用父类构造方法需要通过super()调用,访问父类成员变量和方法时需要通过“super.”的形式,访问本类成员变量和方法还是通过“this.”的形式。
public class Test1 {
public static void main(String[] args) {
HuaWei huaWei = new HuaWei();
huaWei.setBrandName("华为");
huaWei.setPrice(7999);
huaWei.setVersion("mate60");
huaWei.call();
huaWei.text();
iPhone iPhone = new iPhone();
iPhone.setBrandName("苹果");
iPhone.setPrice(8999);
iPhone.setVersion("iPhone15");
iPhone.call();
iPhone.text();
}
}
通过上面的代码就可以控制子类的属性和方法输出结果了。
3.继承的好处
- 继承提高了代码的复用性,父类中定义过得属性和方法,子类可以直接拿来用,无需重复定义
- 继承提高了代码的可维护性,父类中某个方法出现bug,只需要修改父类的方法逻辑就可以了,子类无论是否重写了这个方法都不会受到影响
- 继承让类与类之间产生了关系,为多态提供了前提条件
三、多态
1.什么是多态?
多态就是一个对象具有的多种状态,具体表现为父类引用指向子类实例。
public class Test1 {
public static void main(String[] args) {
// 当前事物, 是一只猫
Cat c = new Cat();
c.eat();
// 当前事物, 是一只动物
Animal a = new Cat();
a.eat();
}
}
class Animal {
public void eat(){
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
多态的三个必要条件:
- 要有继承关系
- 子类要重写父类的方法
- 要有父类引用指向子类对象
2.为什么要使用多态?
为了让一个父类型变量引用不同的子类对象,从而使具体的方法逻辑随着子类对象的改变而改变。
下面的代码简单定义了一个Animal类,并用Cat类和Dog类继承它。另外定义了一个方法useAnimal,形参为Animal对象,当传入参数为Animal的子类对象时,这个方法会自动调用不同对象对应的eat方法。
public class Test2 {
public static void main(String[] args) {
//这里就是根据传入对象的不同改变方法的执行逻辑
useAnimal(new Cat());
System.out.println("-------------------");//分隔符
useAnimal(new Dog());
System.out.println("-------------------");
//向上转型:
Animal a1 = new Dog();
a1.eat();//这里执行Dog类的成员方法,因为对于成员方法来说,编译看父类,运行看子类
a1.drink();//由于子类无法重写父类静态方法,所以这个静态方法执行的还是父类的,等同于Animal.drink()
System.out.println(a1.i);//这里打印的还是10,因为对于成员变量来说,编译和运行都看父类。
System.out.println("-------------------");
Animal a2 = new Cat();
//向下转型:
Cat cat = (Cat) a2;
cat.catchMouse();
}
public static void useAnimal(Animal animal){//这里传入的参数如果是子类对象,就默认发生了向上转型
animal.eat();
System.out.println(animal.i);
//在这个方法中调用Cat特有的方法就需要判断传入的对象是不是Cat类型
if (animal instanceof Cat c){
c.catchMouse();
}
}
}
class Animal{
int i = 10;
public void eat(){
System.out.println("Animal eat...");
}
public static void drink(){ //子类不能重写父类的静态方法
System.out.println("Animal drink...");
}
}
class Cat extends Animal{
int i = 20;
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃肉");
}
public static void drink(){
System.out.println("狗喝水");
}
}
总结起来有这么几点:
- 通过多态可以调用到父类被子类重写的成员方法(编译时看父类,运行时看子类),但只能调用到父类的成员变量(编译时看父类,运行时也看父类)
- 子类特有的成员变量和成员方法无法通过多态调用,只能实例化对象再调用
- 父类的静态方法无法被子类重写,即使子类以定义了与父类一模一样的静态方法,通过多态也只能调用到父类的静态方法
- 如果一个方法的形参是父类对象,那么要想在这个方法中调用某个子类(比如上面代码中的Cat类)的特有方法,就必须用instanceof关键字判断实参是不是这个类的实例对象,是的话才能调用
3.多态的好处
多态的好处在于增强了代码的扩展性和灵活性,通过传入同一类型的父类对象,执行不同子类的方法逻辑,无需根据形参是父类对象还是子类对象定义不同的方法,某种程度上也提高了开发效率。