面向对象的三大特征:
- 封装
- 继承
- 多态
1. 继承和 Java
继承的本质是提炼出公用代码,避免重复。
Java 的继承体系是单根继承,所有类都继承自Object类,重要的方法比如equals()和toString()。
C++ 这种多重继承带来的问题就是当两个父类有相同的东西时,子类无法做出选择。
例如,将属性name 和方法sayMyName 抽离到公共的Animal类。
public class Animal {String name;public void sayMyName() {System.out.println("我的名字是" + name);}}
public class Cat extends Animal {public Cat(String name) {this.name = name;}public void meow() {System.out.println("喵" + name);}}
public class Dog extends Animal {public Dog(String name) {this.name = name;}public void wang() {System.out.println("汪" + name);}}
public class Main {public static void main(String[] args) {Cat cat = new Cat("ABC");cat.meow();cat.sayMyName();Dog dog = new Dog("BCD");dog.wang();dog.sayMyName();}}
2. 类的结构与初始化顺序
- 子类拥有父类的一切数据和行为
- 父类先于子类进行初始化
- 必须拥有匹配的构造器
super关键字
上面例子中,如果父类和子类都没有显式的声明构造器,那么编译器会自动给父类和子类添加默认的构造器:
public class Animal {//...public Animal() {// 一个空的构造器}//...}
public class Cat extends Animal {public Cat() {super(); // 超类的构造器}// ...}
如代码中所示,super 关键字代表的就是父类的构造器,子类来源于父类,如此,当调用Cat类的构造器创建Cat实例时,内部先调用super创建出父类的成员,然后再创建自身的成员。
还是上面的例子,如果父类一开始就自定义了构造器,那么子类中就要拥有匹配的构造器,编译器提供的默认构造器是没有参数的,这不匹配,所以需要子类的构造器中调用父类,并传入参数:
public class Animal {String name;// 父类自定义了构造器public Animal(String name) {this.name = name;}public void sayMyName() {System.out.println("我的名字是" + name);}}
public class Cat extends Animal {// 创建 Cat 实例时,会先创建 Animal 实例// 所以要给父类的构造器 super 中传递 new Cat 时的参数public Cat(String name) {super(name);}// ...}
3. 实例方法的 Override
又称为覆盖/重写,发生在继承时对父类方法的覆盖,且返回类型、方法名以及参数列表保持一致才算是覆盖。
注意和重载(Overload)的区别,重载是在同一个类或者子类与父类中定义名字相同,但参数不同的方法(两同一不同),可以实现方法的默认值等功能,这里有提到重载。
永远使用@Override注解来防止手残,善用 IDEA 中的提示。
在 Java 中随处可见 Override,比如String.equals。
public class User {private Integer id;private String name;public User(Integer id, String name) {this.id = id;this.name = name;}public Integer getId() {return id;}public String getName() {return name;}public static void main(String[] args) {System.out.println(new User(1, "user1") == new User(1, "user1"));System.out.println(new User(1, "user1").equals(new User(1, "user1")));System.out.println(new User(1, "user1").equals(new User(2, "user2")));}// 请在这里覆盖equals方法,使得两个相同ID的用户equals返回true@Overridepublic boolean equals(Object obj) {if (obj instanceof User) {return this.id.equals(((User)obj).id);} else {return false;}}}
4. 设计模式实战:模板方法模式
提供一个“模板”类,使用时可以覆盖模板的全部或部分。
如下所示,Story 是模板,MonsterStory继承自前者,属于模板的消费者:
public class MonsterStory extends Story {// 请补全本类,使得main方法可以输出以下内容://// 开始讲故事啦// 从前有个老妖怪// 故事讲完啦// 你还想听吗public static void main(String[] args) {new MonsterStory().tellStory();}@Overridepublic void story() {System.out.println("从前有个老妖怪");}@Overridepublic void endStory() {super.endStory(); // 部分覆盖时,先保留父类的实现System.out.println("你还想听吗"); // 再在此基础上增加自己的实现}}
public class Story {public final void tellStory() { // final 定义的方法可以防止被 OverridestartStory();story();endStory();}public void startStory() {System.out.println("开始讲故事啦");}public void story() {System.out.println("从前有个老和尚");}public void endStory() {System.out.println("故事讲完啦");}public static void main(String[] args) {new Story().tellStory();}}
5. 向上/向下转型
- 一个子类类型的对象永远也是一个父类类型的对象
- instanceof 判断类型
- null instanceof dataType 永远是 false
- null 永远可以赋值给一个对象
- 因此,当需要一个父类型时,总可以传递一个子类型
- 但是有时必须进行一些转型,转型是不安全的,所以一定要使用强制类型进行转换:
long i = a;int b = (int) i;
6. final 关键字
在 Java 中,声明类、变量和方法(和方法的参数)时,可使用关键字 final 来修饰,查一下单词,final代表 最终的、决定性的、不可更改的,所以特点如下:
final修饰的变量是常量,初始化后不能再被赋值(非static成员变量可以晚点再初始化);final修饰的方法不能被子类 Override;final修饰的类不能被继承(因为继承提供了灵活也带来隐患);- 常量的命名约定是全大写加下划线;
final声明的东西不可变,所以编译时会进行一定优化,比如内联到字节码中,可以提高性能。
7. 设计模式实战:单例模式(Singleton pattern)
单例模式限制类的实例化和个数,提供全局的方法来获取唯一实例。
public class World {// 创建唯一的实例对象private static final World SINGLETON_INSTANCE = new World();// 将构造器私有化,这样该类就不会(轻易)再被实例化private World() {}// 暴露出获取可用对象的方法(就像工厂模式一样)public static World getInstance() {return SINGLETON_INSTANCE;}}
