类与对象

类有成员+方法

  1. public class MyClass{
  2. private int i;
  3. MyClass(int value){
  4. i = value;
  5. }
  6. public void add(){
  7. this.i++;
  8. }
  9. public void get(){
  10. return i;
  11. }
  12. }

实例化

类可实例化成对象,除了静态方法和成员,只有实例化成对象才能使用。

抽象类

通过关键字abstract修饰,抽象类不可实例化

继承

关键字extends
子类会继承父类带有的变量和方法
子类只可继承自一个父类
子类可以在原有父类所带有的变量和方法的基础上,增加其他方法或成员
通过@Override注解,可对父类的方法进行重写。

  1. public class Animal{
  2. private int age;
  3. public void eat(){
  4. // do something;
  5. }
  6. public void sleep(){
  7. // do something;
  8. }
  9. }
  10. public class Dog extends Animal{
  11. @Override
  12. public void eat(){
  13. System.out.println("eat");
  14. }
  15. public void eat(String food){
  16. System.out.println("eat " + food);
  17. }
  18. @Override
  19. public void sleep(){
  20. System.out.println("sleep");
  21. }
  22. }

接口

  • 关键字interface定义接口
  • 接口可以定义成员变量和方法

    • 成员变量一定是final常量,默认用finalstatic修饰,无需显式声明,且定义时,必须赋值
      1. public interface FatherI{
      2. // 成员变量,已用final修饰
      3. int a = 1;
      4. // 定义方法,默认已使用public abstract 修饰,实现类必须将方法进行实现
      5. void sleep();
      6. }
  • 类使用关键字implements实现接口

  • 接口使用关键字extends拓展一个或多个的接口
  • 在Java8中,为接口引入静态默认方法,这时候接口与抽象类的界限变得模糊起来。

    • 默认方法用default关键字修饰,通过实现类的实例化对象才能进行调用
    • 静态方法用static关键字修饰,可直接通过接口调用

      • 在Java9中可用private修饰,默认为publicprivate的静态方法只能在接口中访问,即使是在接口的实现类中也不可使用 ```java public interface FatherI{ // 抽象方法,必须被实现类重写(除非实现类是抽象类) void abstractMethod(); // 静态方法 public static void staticMethod(){ System.out.println(“test-FatherI-staticMethod”); } // 私有静态方法 private static void privateStaticMethod(){ System.out.println(“test-FatherI-staticMethod”); } // 默认方法,可被实现类重写 //访问权限为public,我很好奇在后面的版本会不会增加private修饰 default void defaultMethod(){ // 私有静态方法,只可接口内部访问,所以只有接口中实现的方法才可以调用 FatherI.privateStaticMethod(); } } public class FatherImpl implements FatherI{

      @Override public void abstractMethod(){ System.out.println(“test-abstractMethod”); }

      public static void main(String[] args) { FatherImpl fatherImpl = new FatherImpl();

      // 可用,通过实现类的实例化对象调用重写的方法 fatherImpl.abstractMethod();

      //!!!不可用,通过实现类调用接口静态方法 FatherImpl.staticMethod();

      //!!!可用,调用接口静态方法 FatherI.staticMethod();

      //!!!不可用,在其他类中调用私有静态方法 FatherI.privateStaticMethod();

      // 可用,通过实现类的实例化对象调用接口默认方法 fatherImpl.defaultMethod();
      } } ```

  • 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。 ```java public class FatherC{ private int age; public void test(){

    1. System.out.println("test-FatherC")

    } }

public interface FatherI { // 接口默认方法 public default void test(){ System.out.println(“test-FatherI”) } }

public class Son extends FatherC implements FatherI{ private String name; }

public class Main{ public static void main(String[] args){ Son son = new Son(); son.test(); // 输出 test-FaC // 会优先调用父类FaC的test()方法 } }

  1. - 一个类可以实现多个接口,可以弥补抽象类的单继承设计
  2. ```java
  3. public interface FatherIA{
  4. void sleep();
  5. default void defaultMethod(){
  6. System.out.println("defaultMethod-FatherIA");
  7. }
  8. }
  9. public interface FatherIB{
  10. void sleep();
  11. default void defaultMethod(){
  12. System.out.println("defaultMethod-FatherIB");
  13. }
  14. }
  15. public class FatherImpl implements FatherIA,FatherIB{
  16. @Override
  17. public void sleep(){
  18. System.out.println("sleep");
  19. }
  20. // 当实现的多个接口,具有相同的默认方法(与方法重载类似),实现类必须自行实现
  21. @Override
  22. public void defaultMethod(){
  23. System.out.println("defaultMethod-FatherImpl");
  24. }
  25. }

封装

隐藏对象的内部状态和功能,仅对外提供公共访问方式。
如下所示,nameheight属性都设为私有,防止通过person.height直接访问对象的属性,而允许通过get/set方法进行访问和修改的方式,就是一个典型的封装行为。

  1. class Person {
  2. private String name;
  3. private double height;
  4. private int age;
  5. public void setName(String name){
  6. this.name = name;
  7. }
  8. public String getName(){
  9. return name;
  10. }
  11. public void setHeight(double height){
  12. if(height < 0){
  13. return;
  14. }
  15. this.height = height;
  16. }
  17. public double getHeight(){
  18. return height;
  19. }
  20. public void setAge(int age){
  21. if(age < 0){
  22. return;
  23. }
  24. this.age = age;
  25. }
  26. public int getAge(){
  27. return age;
  28. }
  29. public boolean isAdult(){
  30. return age >= 18;
  31. }
  32. }
  33. class Test{
  34. public static void main(String args[]){
  35. Person person = new Person();
  36. // 不可用
  37. person.height = 1.70;
  38. // 可用
  39. person.setHeight(1.70);
  40. }
  41. }

好处

  • 可以隐藏内部细节,对外提供公共访问方式,提高安全性
    • setHeight时,可以对height的值进行验证,隐藏了内部细节,提高了安全性。
  • 提高代码的复用性

    • 还是考虑上面的代码,当其他类调用PersonisAdult()方法时,直接调用即可得到结果,当有一天法定成年年龄发生改变,只需改变isAdult()的实现,而调用者则无需做出任何更改。

      多态(重载与重写)

      多态的定义还挺有争议的,有人把方法重载也算进在,这里也采取这种广义的理解(尽管我觉得不算👀。
      Java中分为编译时多态(即方法重载Overload)和运行时多态(即方法重写Override
      编译时多态(重载)
      编译时多态即是方法/运算符的重载,Java中并不支持运算符重载(但我觉得未来可期:>)
      同一个事物在不同的情况下具有不同的行为,比如当传入的参数的个数,类型这些不同时,进行不一样处理,这称为多态。‎
      当有多个具有相同名称但参数不同的方法时,则这些方法称为‎‎重载‎‎。方法可以通过参数数量的更改或/和参数类型的更改来重载。‎
      1. public class MyClass{
      2. private int i;
      3. public void add(){
      4. i++;
      5. }
      6. public void add(int a){
      7. i+=a;
      8. }
      9. }
      运行时多态(重写)
      简单来说是同一个类的对象,做同一件事,表现出不同的行为
      以下图为例
  • Shape为父类,CirleSquare为子类,均实现了draw()方法和move()方法

  • ShapeController类具有drawShape()方法,接受一个shape类型的参数 OOP - 图1```java public class Shape{ void draw(){
    1. System.out.println("draw-default");
    } void move(){
    1. System.out.println("move-default");
    } } class Circle extends Shape { @Override public void draw(){
    1. System.out.println("draw-circle");
    } @Override public void move(){
    1. System.out.println("move-circle");
    } } class Square extends Shape{ @Override public void draw(){
    1. System.out.println("draw-square");
    } @Override public void move(){
    1. System.out.println("move-square");
    } } class ShapeController{ public static void main(String[] args){
    1. Circle circle = new Circle();
    2. Square square = new Square();
    3. drawShape(circle);
    4. drawShape(square);
    } public void drawShape(Shape shape){
    1. shape.draw();
    } }
    1. ```java
    2. public class Shape{
    3. void draw(){
    4. System.out.println("draw-default");
    5. }
    6. void move(){
    7. System.out.println("move-default");
    8. }
    9. }
    10. class Circle extends Shape {
    11. @Override
    12. public void draw(){
    13. System.out.println("draw-circle");
    14. }
    15. @Override
    16. public void move(){
    17. System.out.println("move-circle");
    18. }
    19. }
    20. class Square extends Shape{
    21. @Override
    22. public void draw(){
    23. System.out.println("draw-square");
    24. }
    25. @Override
    26. public void move(){
    27. System.out.println("move-square");
    28. }
    29. }
    30. class ShapeController{
    31. public static void main(String[] args){
    32. Circle circle = new Circle();
    33. Square square = new Square();
    34. drawShape(circle);
    35. drawShape(square);
    36. }
    37. public void drawShape(Shape shape){
    38. shape.draw();
    39. }
    40. }

// todo

  • 编译器在编译时,并不直接能确定传入drawShape()方法的参数具体是什么类型,只能确定传入的一定是Shape类型。那这时候,通过 Shape s = new Circle();的方式,创建一个Circle对象,调用ShapeControllerdrawShape(s)方法,这时候会调用Circle类重写的draw方法,同理,当传入一个Square类给drawShape(s),会调用Square类的draw方法。同一个对象,在传入对象不同的自动进行不同行为即为多态。

为了解决这个问题,面向对象程序设计语言使用了后期绑定的概念,当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值进行类型检查,但是并不知道将被执行的确切代码。 为了执行后期绑定,Java使用一小段特殊的代码来代替绝对地址的调用。这段代码使用对象中存储的信息来计算方法体的地址。这样,根据这一小段代码的内容,每一个对象都可以具有不同的行为表现。当向一个对象发送消息时,该对象就能直到对这条消息该做些什么 ——《Java编程思想》第四版,第一章第9页,陈昊鹏译 评论👀:”向对象发送消息”,不知道是原文如此还是译者自己的译法,这句好实在不好理解 不过总的来说,在这儿提到多态的是实现机制的,还是很受用, 之前觉得多态这种行为是很自然的,理所应该,那实现起来应该也很简单,没想到实现机制还挺复杂