目录
面向对象
封装
封装描述
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
封装优点
-
良好的封装能够增加内聚减少耦合。
-
高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
-
低耦合:仅对外暴露少量的方法用于使用
-
-
通过隐藏对象的属性来保护对象内部的状态,方便修改和实现。(隐藏信息、实现细节)。
-
提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展(将变化隔离,类内部的结构可以自由修改,增加内部实现部分的可替换性)。
-
禁止对象之间的不良交互提高模块化(良好的封装能够减少耦合)。
-
可以对成员变量进行更精确的控制。
-
容易保证类内部数据间的一致性,从而提高软件的可靠性。
权限修饰符
Java的权限修饰符共有4种,分别为public
,protected
、缺省
、private
;权限修饰符可以使得数据在一定范围内可见或者隐藏。
修饰符 | 本类 | 本包 | 同一个包中 | 子类 | 任意位置 |
---|---|---|---|---|---|
private | √ | × | × | × | × |
缺省 | √ | √ | × | × | × |
protected | √ | √ | √ | √ | × |
public | √ | √ | √ | √ | √ |
封装实现
将成员变量(field)私有化(用private修饰),并且提供公共(public)的get/set方法即可,我们把这种成员变量也称为属性(property)。
public class Person { private String name;//姓名 private int age;//年龄 private String sex;//性别 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 String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
封装之后,就不能再用Person类的实例来调用其属性了,会提示该属性是私有化的,不能调用,这样就保证了属性的隐蔽性。我们会提供属性public的getter和setter方法来对属性进行操作。
继承
继承描述
Java中继承就是子类继承父类的一些特征和行为,使得子类对象(实例)具有父类相同的特征和行为,当然子类也可以有自己的特征与行为。多个子类中存在相同的特征与行为,那么就可以将这些内容抽取到父类中,而父类中的一些特征和行为可以被子类继承下来使用,不再需要再在子类中重复定义了。
继承特点
-
Java只支持单继承 ,不支持多继承(如A继承B,A继承C),但支持多层继承(如A继承B,B继承C) 。
-
子类拥有父类非private的属性,方法。(其实子类继承父类后,仍然认为获取了父类的private结构,只是因为封装的影响,使得子类不能直接调用父类的结构而已)
-
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系)。
继承优点
-
减少代码的冗余,提高代码的复用性。
-
便于功能的扩展。
-
为后面多态的使用提供了前提。
构造器
构造器也叫构造方法,构造器的作用是用于创建并初始化对象。当我们在使用new
关键字创建对象时,如A a = new A(); 此时使用了无参构造器创建了一个对象,对象创建完成后它的成员变量也会被初始化,默认是相对数据类型的默认值。可以使用有参的构造器。构造器只为实例变量初始化,不为静态类变量初始化。
构造器创建的注意事项:
-
构造器名必须与它所在的类名必须相同
-
它没有返回值,所以不需要返回值类型,甚至不需要void
-
如果你不提供构造器,系统会给出默认无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
-
如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
-
构造器是可以重载的,既可以定义参数,也可以不定义参数。
方法重载
又称编译时多态。指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
方法重写
又称运行时多态。子类中定义与父类中相同的方法,一般方法体不同,用于改造并覆盖父类的方法。当子类继承了父类的某个方法之后,发现这个方法并不能满足子类的实际需求,那么可以通过方法重写,覆盖父类的方法。
方法重写特点:
-
必须保证父子类之间方法的名称相同,参数列表也相同。
-
子类方法的返回值类型必须与父类方法的返回值类型相同或者为父类方法返回值类型的子类类型。
-
子类方法的访问权限必须不能小于父类方法的访问权限。(public > protected > 缺省 > private)
-
子类方法抛出的异常不能大于父被重写的异常。
-
静态方法不能被重写,方法重写指的是实例方法重写,静态方法属于类的方法不能被重写,而是隐藏。
-
私有等在子类中不可见的方法不能被重写。
-
final方法不能被重写。
多态
多态描述
父类的引用指向子类的实例对象。多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编译时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态满足条件
1、继承:在多态中必须存在有继承关系的子类和父类。
2、重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
3、向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
多态实现
编译看左边,运行看右边。在编译期间,只能调用父类中声明的方法,但是在运行期间,实际执行的是子类重写父类的方法。
public class Instrument { public void play() { System.out.println("Instrument is playing..."); } } public class Wind extends Instrument { public void play() { System.out.println("Wind is playing..."); } } public class Percussion extends Instrument { public void play() { System.out.println("Percussion is playing..."); } } public class Music { public static void main(String[] args) { List<Instrument> instruments = new ArrayList<>(); instruments.add(new Wind()); instruments.add(new Percussion()); for(Instrument instrument : instruments) { instrument.play(); } } }
向上转型
子类引用的对象转换为父类类型称为向上转型。子类对象转为父类的引用。此处父类对象也可以是接口。如果在向上转型时,子类并没有重写父类的方法,那么调用的就是父类中的方法。向上转型的主要原因是为了实现多态性。多态性允许我们使用父类类型的引用来引用子类对象。通过向上转型,我们可以将不同的子类对象赋值给相同的父类引用,从而实现对这些对象的统一处理。另一个原因是为了提高代码的灵活性。通过向上转型,我们可以在不改变现有代码的情况下,轻松地扩展程序的功能。当我们需要添加新的子类时,只需要创建一个新的子类对象并将其赋值给父类引用即可,而不需要修改现有的代码。
public class Animal { public void eat() { System.out.println("动物吃东西"); } public void skill() { System.out.println("动物有本领"); } } class Dog extends Animal { public void eat() { System.out.println("狗吃骨头"); } public void skill() { System.out.println("狗会看家"); } //新添加的方法 public void run() { System.out.println("狗跑得快"); } } class Cat extends Animal { public void eat() { System.out.println("猫吃鱼"); } public void skill() { System.out.println("猫会抓老鼠"); } //新添加的方法 public void life() { System.out.println("猫有九条命"); } } //测试类 public class AnimalTest { public static void main(String[] args) { Animal dog = new Dog();//向上转型成Animal dog.eat(); dog.skill(); Animal cat = new Cat();//向上转型成Animal cat.eat(); cat.skill(); } }
向下转型
向下转型是把父类对象转为子类对象。需要向下转型的主要原因是在某些特定的情况下,我们需要使用子类特有的方法或属性。当一个对象被向上转型为父类类型时,它会失去其子类特有的方法和属性,只能访问父类中定义的方法和属性。但是,如果我们确切地知道一个对象的真实类型是其子类类型,我们可以使用向下转型来恢复其子类特有的功能。向下转型并不总是安全的。如果我们将一个父类对象向下转型为一个与其真实类型不匹配的子类类型,会导致ClassCastException异常。因此,在进行向下转型之前,我们应该使用instanceof运算符来检查对象的类型,以确保转型是安全的。
向下转型注意事项
-
向下转型的前提是父类对象指向的是子类对象(也就是说,在向下转型之前,它得先向上转型)
-
向下转型只能转型为本类对象,否则会导致ClassCastException异常。
public class AnimalTest { public static void main(String[] args) { //Dog向上转型成Animal Animal dog = new Dog(); //向下转型为Dog Dog dog1 = (Dog) dog; dog1.eat(); dog1.skill(); //java.lang.ClassCastException: com.thr.java2.Dog cannot be cast to com.thr.java2.Cat Cat cat = (Cat) dog; cat.eat(); cat.skill(); } }
向下转型我们一般会使用 instanceof 关键字来判断:
public class AnimalTest { public static void main(String[] args) { AnimalTest test = new AnimalTest(); test.show(new Animal()); test.show(new Dog()); test.show(new Cat()); } public void show(Animal animal) { if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.eat(); dog.skill(); dog.run(); } } }
经典案例
首先是子类B类向上转型为父类A,而子类B有重写了父类A中的show(A obj)方法,所以a2变量能调用的只有父类A类中的show(D obj)和子类B中的show(A obj),而B是继承自A类的,D继承自B类,所以不可能调用show(D obj)方法,所以结果是4--B and A;剩下的依次类推。
当父类对象变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找。但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
public class A { public String show(D obj) { return ("A and D"); } public String show(A obj) { return ("A and A"); } } public class B extends A { public String show(B obj) { return ("B and B"); } public String show(A obj) { return ("B and A"); } } public class C extends B { } public class D extends B { } public class Test { public static void main(String[] args) { A a1 = new A(); A a2 = new B(); B b = new B(); C c = new C(); D d = new D(); System.out.println("1--" + a1.show(b)); System.out.println("2--" + a1.show(c)); System.out.println("3--" + a1.show(d)); System.out.println("4--" + a2.show(b)); System.out.println("5--" + a2.show(c)); System.out.println("6--" + a2.show(d)); System.out.println("7--" + b.show(b)); System.out.println("8--" + b.show(c)); System.out.println("9--" + b.show(d)); } } 结果: 1--A and A 2--A and A 3--A and D 4--B and A 5--B and A 6--A and D 7--B and B 8--B and B 9--A and D
多态总结
1、Java多态也可以用一句话表示:父类的引用指向子类的对象。
2、运行时多态的前提:继承,重写,向上转型。
3、多态能够减少重复代码,使代码变得简洁;提高了代码的扩展性。
4、多态其实是一种虚拟方法调用。归结为一句话:编译看左边,运行看右边。
5、向上转型就是是将子类对象转为父类对象。
6、继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。