java面向对象基础

java面向对象基础

抽象类

  • 抽象类与接口都是实现面向抽象编程的一种手段。

  • 如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。类也必须用abstract修饰。

  • 抽象方法实际上相当于定义了“规范”。

  • 面向抽象编程的本质就是:

    • 上层代码只定义规范(例如:abstract class Person);

    • 不需要子类就可以实现业务逻辑(正常编译);

    • 具体的业务逻辑由不同的子类实现,调用者并不关心。

public class Main {
   public static void main(String[] args) {
       Person p = new Student();//抽象类引用具体实现子类
       p.run();
  }
}

abstract class Person {
   public abstract void run();
}

class Student extends Person {
   @Override
   public void run() {
       System.out.println("Student.run");
  }
}

接口

  • 接口定义的所有方法都是public abstract的,不需要写出来。

  • 接口不能定义字段

  • 接口还可以继承另一个接口,使用extends关键字。

  • 可以定义default方法。子类可以不实现default方法。

public class Main {
   public static void main(String[] args) {
       Person p = new Student("Xiao Ming");
       p.run();
  }
}

interface Person {
   String getName();
   default void run() {
       System.out.println(getName() + " run");
  }
}

class Student implements Person {
   private String name;

   public Student(String name) {
       this.name = name;
  }

   public String getName() {
       return this.name;
  }
}

静态字段和方法

  • 不推荐用实例变量.静态字段/方法去访问静态字段/方法,因为在Java程序中,实例对象并没有静态字段/方法。在代码中,实例对象能访问静态字段/方法只是因为编译器可以根据实例类型自动转换为类名.静态字段方法来访问静态对象。

  • 因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。

  • 静态方法经常用于工具类。

  • 但是,interface是可以有静态字段的,并且静态字段必须为final类型:

public interface Person {
   public static final int MALE = 1;
   public static final int FEMALE = 2;
}

实际上,因为interface的字段只能是public static final类型,所以我们可以把这些修饰符都去掉。


  • 要特别注意:包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

import

导入别的包的类有三种方式:

  1. 直接写出完整类名

// Person.java
package ming;

public class Person {
   public void run() {
       mr.jun.Arrays arrays = new mr.jun.Arrays();
  }
}
  1. import语句,导入小军的Arrays,然后写简单类名:

// Person.java
package ming;

// 导入完整类名:
import mr.jun.Arrays;

public class Person {
   public void run() {
       Arrays arrays = new Arrays();
  }
}

在写import的时候,可以使用*,表示把这个包下面的所有class都导入进来(但不包括子包的class)。一般不推荐这种写法,因为在导入了多个包后,很难看出Arrays类属于哪个包。

  1. 还有一种import static的语法,它可以导入可以导入一个类的静态字段和静态方法:

package main;

// 导入System类的所有静态字段和静态方法:
import static java.lang.System.*;

public class Main {
   public static void main(String[] args) {
       // 相当于调用System.out.println(…)
       out.println("Hello, world!");
  }
}

import static很少使用。

Java编译器最终编译出的.class文件只使用完整类名,因此,在代码中,当编译器遇到一个class名称时:

  • 如果是完整类名,就直接根据完整类名查找这个class

  • 如果是简单类名,按下面的顺序依次查找:

    • 查找当前package是否存在这个class

    • 查找import的包是否包含这个class

    • 查找java.lang包是否包含这个class

因此,编写class的时候,编译器会自动帮我们做两个import动作:

  • 默认自动import当前package的其他class

  • 默认自动import java.lang.*


访问权限

  • 类的修饰符只有publicpackage两种。要么可以获取这个类的实例要么只能是同包的类才能获取这个类的实例。

  • 对类、字段以及方法能不能获取到有三种场景:能不能new 一个对象对象.field or 对象.method,子类继承父类能不能使用父类的字段/方法。

  • 能不能访问到一个类的字段/方法首先看能不能实例这个类(即目标类的修饰符是public还是package,然后看方法/字段的修饰符。

四种修饰符:

  1. public。可以被其他任何类访问。

  2. private。只能在类内部使用,使用对象.字段或方法或者子类内部都不能使用。

  3. protected。子类内部可以使用父类的protected字段/方法。与public区别在于不同包目标类实例无法使用protected方法/字段。

package com.bi;

public class Person {
   private int i;
   protected int j;
   protected void test(){
       System.out.println("aa");
  }
}
package com.bi.wang;    //不同包

import com.bi.Person;

public class Wang{
   public static void main(String[] args) {
       Person person = new Person();
       person.j=3; //失败,因为访问的是protected字段
       person.test(); //失败,因为访问的是protected方法
  }
}
package com.bi;//同一个包

public class Main {

    public static void main(String[] args) {
        Person p = new Person();
        p.j=3;//ok
        p.test();//ok
    }
}
package com.bi.wang;//不同包

import com.bi.Person;

public class Wang extends Person{
    void run(){
        test();//ok,因为是子类
        System.out.println(j);//ok
    }
}
  1. package。可以修饰class和方法/字段,不用写出来。package修饰的类、字段、方法只有同一个包才能访问,外包无法获取实例,无法在子类使用package修饰的字段/方法。

    • 同包:

    package com.bi;
    
    class Person  { //package,不用写出来
        int i;//package
        int j;
    
        void test() {//package
            System.out.println("aa");
        }
    }
    package com.bi;
    //同包
    class Main {
    
        public static void main(String[] args) {
            Student student = new Student();
            student.j=33;
            student.run();
            Person person = new Person();
            person.j=44;
            person.test();
        }
    }
    //同包
    class Student extends Person{
        void run(){
            test();
            System.out.println(j);
        }
    }

     

    • 不同包:

    package com.bi;
    
    public class Person  { //package,不用写出来
        int i;//package
        int j;
    
        void test() {//package
            System.out.println("aa");
        }
    }
    package com.bi.wang;//不同包
    
    import com.bi.Person;
    
    public class Wang {
        public static void main(String[] args) {
            Person person = new Person();//可以实例化对象,因为public修饰Person类
            person.j=3;//faild,不同包,这里类似protected
        }
    }
    
    class Junior extends Person{
        void test3(){
            System.out.println(i);//faild,不同包
            test();//faild,不同包
        }
    }

最佳实践:

  1. 类内部public在前,private方法在后,因为暴露给外部的方法优先放在明显的位置。

  2. 如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。

  3. 把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。


final

  1. final修饰class可以阻止被继承。

  2. final修饰method可以阻止被子类覆写。

  3. final修饰field可以阻止被重新赋值。

  4. final修饰局部变量可以阻止被重新赋值。

总结,final修饰就是不要改变的意思。


内部类

如果一个类定义在另一个类的内部,这个类就是Inner Class

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested"); // 实例化一个Outer
        Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
        inner.hello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

上述定义的Outer是一个普通类,而Inner是一个Inner Class,它与普通类有个最大的不同,就是Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。

因为Inner Class除了有一个this指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this访问这个实例。所以,实例化一个Inner Class不能脱离Outer实例。

观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class

Inner class也可以在方法内部定义。

Anonymous Class

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested");
        outer.asyncHello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    void asyncHello() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };
        new Thread(r).start();
    }
}

观察asyncHello()方法,我们在方法内部实例化了一个RunnableRunnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。在定义匿名类的时候就必须实例化它。

之所以我们要定义匿名类,是因为在这里我们通常不关心类名,比直接定义Inner Class可以少写很多代码。

观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1Outer$2Outer$3……

除了接口外,匿名类也完全可以继承自普通类。

Static Nested Class

public class Main {
    public static void main(String[] args) {
        Outer.StaticNested sn = new Outer.StaticNested();
        sn.hello();
    }
}

class Outer {
    private static String NAME = "OUTER";

    private String name;

    Outer(String name) {
        this.name = name;
    }

    static class StaticNested {
        void hello() {
            System.out.println("Hello, " + Outer.NAME);
        }
    }
}

static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this

 

posted @ 2021-01-11 17:14  喜琅琅  阅读(83)  评论(0编辑  收藏  举报