Java面向对象详解

Java OOP

什么是面向对象思想?

把一组数据和处理他们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的的特化(specialization)/泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派(dynamic dispatch). --- 来源于知乎(如何用一句话说明什么是面向对象思想?)

这个定义指出了面向对象的四个特点:抽象、封装、继承和多态

类和对象

对象

具有一组数据和处理这些数据的方法组成一个对象。例如:

对象 person

数据 姓名,年龄

方法 说话,吃饭,睡觉

类可以看成是对象的抽象,是可以生成众多对象的一个模板。例如我们有一个Person类,有姓名年龄两个数据。这就是个模板,它不代表一个人person,但是能够通过这个模板创造出众多person。

// 定义类
权限修饰符 class 类名 {
    属性
    方法  
}

public class Person {
    String name; //名字
    int age;     //年龄
    // 吃饭方法
    void eat() {
        
    }
    // 说话方法
    void speak() {
        
    }
}

构造函数(Constructor)

  • 构造函数概念

    构造函数是用来初始化对象的一段代码块。它具有如下特点:

    1. 没有返回值。
    2. 构造函数的名字和类名必须一致。
    3. 不能有static, final, abstract, synchronised 等关键字修饰。
  • 如何用构造函数初始化对象

    // Java通过new关键字生成对象,并且调用Person类的构造函数初始化这个新的对象
    Person person = new Person();
    
  • 构造函数的类型

    • 默认构造函数: 如果一个类没有构造函数,编译器会在编译阶段自动添加一个无参的构造函数。

      Java面向对象详解_第1张图片
      默认构造函数
    • 无参数的构造函数: 没有参数的构造函数,和默认的构造函数的一个区别是无参的构造函数里面可以有代码,而默认构造函数的函数体里面没有任何代码。

    • 有参数的构造函数: 构造函数有参数

      public class Person {
          int age;
          String name;
          
          Person(int age, String name) {
              this.age = age;
              this.name = name;
          }
          
          public static void main(String[] args) {
              Person person = new Person(18, "Johnny");
              System.out.println(person.name);
              System.out.println(person.age);
          }
      }
      
    > 注意:如果类有构造函数,则编译器不会在编译阶段添加默认构造函数。所以如果在类有有参数的构造函数但是没有无参数的构造函数的情况下,调用无参数的构造函数编译器会报错。
    > ![](https://upload-images.jianshu.io/upload_images/4401597-84dde9ec70a7f84c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/485) 
  • 父类的构造函数---super()

    如果子类的构造函数没有调用父类的特定构造函数,编译器会默认添加调用父类的无参数构造函数(或者默认构造函数) --- super()。

    public class Person {
        int age;
        String name;
        
        public Person() {
            System.out.println("Person init");
        }
        
        public static void main(String[] args) {
            Man man = new Man(18, "Johnny");
        }
        
    }
    
    class Man extends Person {
        
        public Man(int age, String name) {
            System.out.println("Man init");
        }
        
    }
    

    结果:

    Person init
    Man init
    

    如果父类没有默认构造函数(或者说无参构造函数), 则编译器会报错。
    所以需要指定一个父类的构造函数,例如 super(age, name);

    public class Person {
        int age;
        String name;
        
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
            System.out.println("Person init");
        }
        
        public static void main(String[] args) {
            Man man = new Man(18, "Johnny");
            System.out.println(man.name); // Johnny
        }
        
    }
    
    class Man extends Person {
        
        public Man(int age, String name) {
            super(age, name);
            System.out.println("Man init");
        }
        
    }
    

static关键字

static关键字可以修饰类、变量、方法和代码块。被static修饰的成员属于类,而不是对象。
  • static修饰成员变量

    当所有的对象的某个属性都是一样的时候应考虑使用static关键字修饰。例如我们需要表示体育行业的一些人。

    public class Person {
        int age;
        String name;
        String industry = "sport"; // 体育行业
        
        public Person(int age, String name) {
            this.age = age;
            this.name = name;
        }
        
        public static void main(String[] args) {
            Person personA = new Person(30, "姚明");
            Person personB = new Person(31, "刘翔");
            Person personC = new Person(32, "李娜");          
        }
    }
    

    内存布局示意图:

    Java面向对象详解_第2张图片
    内存布局示意图

    存在两个主要问题:1,每个对象都有一片内存存储相同的一个内容;2,如果需要统一修改这个变量,每个对象都需要修改。

    解决方案:用static修饰变量 static String industry = "sport";

    内存布局示意图:

    Java面向对象详解_第3张图片
    内存布局示意图
由于static修饰的属性是公共属性,属于类。可以通过**“类.属性名”** 的方式进行获取和修改。例如

```
Person.industry = "music";              // 修改
System.out.println(Person.industry);  // 获取 
```
  • static修饰方法

    Java的类的方法是存储在静态存储区的。如下图:

    内存布局示意图:


    Java面向对象详解_第4张图片

    事实上,多个对象对应的是同一个方法,也就是说所有对象的方法是相同的。 static修饰方法在数据存储方面没有区别。最大的作用是可以用 “类.方法名”直接调用,避免了先要new出对象的繁琐和资源消耗

    Person.talk();

  • static代码块(static block)

    静态代码块的作用是统一初始化静态变量,只会在类加载的时候执行一次, 可以用来优化程序性能。如果有多个静态代码块,会按照书写的顺序依次执行多个静态代码块

    public class StaticBlockDemo {
    
        static int number;
        static String str;
        
        static { // 静态代码块1执行1次
            number = 10;
            str = "name";
        }
        
        static { // 静态代码块2,在静态代码块1之后执行1次
            number = 20;
            str = "name2";
        }
        
        public static void main(String[] args) {
            System.out.println(number);
            System.out.println(str);
        }
        
    }
    

    知识扩展:静态代码块、构造代码块、构造函数和普通代码块的区别?

    1. 执行顺序:静态代码块 -> 构造代码块 -> 构造函数 -> 代码块
    2. 执行次数:静态代码块只在类的加载时执行一次;构造代码块在每次构造函数调用前调用,如果有多个构造函数,特定构造函数有可能执行也可能不执行;代码块在函数执行时调用。
    public class StaticBlockDemo {
        
        static int number;
            
        static {
            number = 10;
            System.out.println("静态代码块");
        }
            
        {
            System.out.println("构造代码块");
        }
            
        public StaticBlockDemo() {
            System.out.println("构造函数");
        }
            
        public void func() {
            {
                int a = 10;
                System.out.println("代码块");
            }
        }
            
        public static void main(String[] args) {
            new StaticBlockDemo().func(); // 静态代码块 -> 构造代码块 -> 构造函数 -> 代码块  
        }
            
    }
    
  • 静态内部类(static class)

    如果一个类要被声明为static的,只有一种情况,就是静态内部类

    • 静态内部类只能访问静态的成员变量和方法;
    • 静态内部类不需要引用外部类
    public class Outer {
        
        int age;  // 普通变量
        static String name; // 静态变量
        
        static { // 静态代码块
            name = "Johnny";
        }
        
        { // 构造代码块
            age = 20;
        }
        
        // 静态内部类
        public static class StaticInner {
            public void sayName() {
                System.out.println(name);
            }
        }
        
        // 普通的内部类
        public class NormalInner {
            public void sayAge() {
                System.out.println(age);
            }
        }
        
        public static void main(String[] args) {
            // 静态内部类的初始化
            StaticInner staticInner = new Outer.StaticInner();
            staticInner.sayName(); // Johnny
            
            // 普通内部类的初始化
            Outer outer = new Outer();
            NormalInner normalInner = outer.new NormalInner();
            normalInner.sayAge(); // 20
        }
        
    }
    

继承(Inheritance)

继承是指一个类获取另外一个类的属性和方法的过程,继承的主要目的是复用代码。获取另外一个类的特性的类被称为子类,而特性被另外一个类利用的类则相对应称为父类。
  • 继承语法 --- extends

    class Son extends Parent
    {
    }
    
  • 继承类型

    Java面向对象详解_第5张图片

    Java的类只支持前面三种继承类型:

    • 单继承:一个类只继承另外一个类
    Class A
    {
       public void methodA()
       {
         System.out.println("Method A");
       }
    }
    
    Class B extends A
    {
       public void methodB()
       {
         System.out.println("Method B");
       }
       
       public static void main(String args[])
       {
         B b = new B();
         b.methodA(); //可以调用父类的方法
         b.methodB(); //调用本地方法
      }
    }
    
    • 多级继承:一级一级继承,有多个层次的继承关系
    Class A {
       public void methodA() {
         System.out.println("Class A method");
       }
    }
    
    Class B extends A {
        public void methodB() {
            System.out.println("class B method");
        }
    }
    
    Class C extends B {
       public void methodC() {
         System.out.println("class C method");
       }
       
       public static void main(String args[]) {
         C c = new C();
         c.methodA(); //可以调用祖父类的方法
         c.methodB(); //可以调用父类的方法
         c.methodC(); //调用本地方法
      }
    }
    
    • 层次继承:多个类继承自同一个父类
    class A {
       public void methodA() {
          System.out.println("method of Class A");
       }
    }
    
    class B extends A {
       public void methodB() {
          System.out.println("method of Class B");
       }
    }
    
    class C extends A {
      public void methodC() {
         System.out.println("method of Class C");
      }
    }
    
    class D extends A {
      public void methodD() {
         System.out.println("method of Class D");
      }
    }
    
    class JavaExample
    {
      public static void main(String args[]) {
         B obj1 = new B();
         C obj2 = new C();
         D obj3 = new D();
         // 所有的类都有A的方法
         obj1.methodA();
         obj2.methodA();
         obj3.methodA();
      }
    }
    

    Java的类不支持多继承是因为如果两个父类有同样的方法,子类不知道到底调用哪个父类的方法,造成混乱。但是Java中有一个接口的概念,是可以多继承的。后面会介绍。

super

super是一个Java关键字,相当于是指当当前对象的直接父类中的成员

  • 获取父类的属性值(也包括static修饰的值)

    class SuperClass {
        int number = 10;
        static int number2 = 30;
    }
    
    public class SubClass extends SuperClass {
        int number = 20;
        
        private void printNumber() {
            System.out.println(number);       // 20
            System.out.println(super.number); // 10
            System.out.println(super.number2);// 30
        }
        
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            subClass.printNumber();
        }
        
    }
    
  • 显示调用父类的无参构造函数或者有参构造函数 --- 详见构造函数章节

  • 方法覆写(Override)

    子类可以直接继承父类的方法,但是有时候需要修改父类的方法,我们称之为方法覆写。方法覆写是指子类定义了一个返回值,参数类型和参数数量都和父类都完全一致的方法。

    注意事项:

    1. 方法覆写是子类的访问权限(具体后面介绍)(Java中有四种访问权限,并且public > default > protected > private)不能比父类的访问权限更严格。例如如果父类的访问权限是default,则子类的访问权限只能是public 或者 default。
    1. private, static, final 修饰的方法不能被覆写,因为这些方法都是本类所独有的。虽然可以在子类里面存在和父类里面一样 static 和 private 所修饰的方法,但是和父类没有任何关系,不能称之为方法覆写。
    • 不需要覆写:正常的情况是直接从父类继承过来方法
    class Parentclass {
        public void print() {
            System.out.println("Parentclass Print");
        }
    }
    
    class Subclass extends Parentclass {
    }
    
    public class Test {
        public static void main(String args[]) {
            Subclass sub = new Subclass();
            sub.print(); // 方法从父类继承而来 ---> Parentclass Print
        }
    }
    
    • 覆写情形一:从父类继承方法逻辑,子类添加一些逻辑
    class SuperClass {
        void print() {
            System.out.println("super print");
        }
    }
    
    public class SubClass extends SuperClass {
        void print() {
            super.print(); //父类的逻辑
            System.out.println("sub print"); //子类的逻辑
        }
        
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            subClass.print();
        }
    }   
    
    • 覆写情形二:子类独立的逻辑
    class SuperClass {
        void print() {
            System.out.println("super print"); //父类的逻辑
        }
    }
    
    public class SubClass extends SuperClass {
        void print() {
            System.out.println("sub print"); //子类的逻辑
        }
        
        public static void main(String[] args) {
            SubClass subClass = new SubClass();
            subClass.print();
        }
    }   
    
  • this && super

    this是指当前对象本身。在构造函数一节已经有接触过this关键字,this和super一样也可以调用属性、调用方法和调用构造函数。那这两个关键字有什么区别呢?

    关键字 this super
    本质 一个指向本对象的指针 一个Java关键字
    调用方法 访问本类中的方法 直接访问父类中的方法
    访问属性 访问本类中的属性,没有继续在父类中寻找 访问父类中的属性
    调用构造器 调用本类构造器,放在构造器首行 调用父类构造器,放在子类构造器的首行

    说明:构造函数里面只能调用一次super()或者this(), 并且super()或者this()必须放在第一行,是Java为了保证创建对象的不出现异常情况。

方法重载(Method Overloading)

方法重载是指一个类可以有多个方法名相同但是方法的参数个数或者参数类型不同的多个方法存在,和构造函数可以有多个类似

  • 重载的类型

    • 参数个数不同

      add(int, int)
      add(int, int, int)
      
    • 参数类型不同

      add(int, int)
      add(int, float)
      
    • 参数的顺序不同

      add(int, float)
      add(float, int)
      

    注意事项: 方法的返回值不同不能算是方法重载,并且编译器会报错。

      int add(int, int)
      float add(int, int)
    

绑定(binding)

绑定是指将一个方法的调用与方法所在的类关联起来。Java中的绑定分为静态绑定和动态绑定,又被称作前期绑定和后期绑定。

  • 静态绑定是指编译器能在编译时确定的绑定。主要是static, private, final 修饰的方法。因为这些方法不能够被子类重写。

    class Human {
        public static void walk() {
            System.out.println("Human walks");
        }
    }
    
    class Boy extends Human{
        
        public static void walk() {
            System.out.println("Boy walks");
        }
        
        public static void main( String args[]) {
    
            Human obj = new Boy();
            Human obj2 = new Human();
            obj.walk();  // Human walks --- 方法绑定的是Human类
            obj2.walk(); // Human walks --- 方法绑定的是Human类
            
        }
    }
    
  • 动态绑定是指编译器不能在编译时确定的绑定。方法覆写是一个典型的动态绑定,因为父类和子类都有相同的方法,只有在运行时才能确定是调用的那个类的方法。

    class Human {
        public void walk() {
            System.out.println("Human walks");
        }
    }
    
    class Boy extends Human{
        
        public void walk() {
            System.out.println("Boy walks");
        }
        
        public static void main( String args[]) {
    
            Human obj = new Boy();
            Human obj2 = new Human();
            obj.walk();  // Boy walks  --- 方法绑定的是Boy类
            obj2.walk(); // Human walks --- 方法绑定的是Human类
        }
    }
    

多态性(Polymorphism)

多态字面意思就是多种状态,例如同样的一个方法具有不同功能的特性,多态也可以分为编译时多态和运行时多态。

  • 运行时多态---方法覆写为代表

    Animal.java

    public class Animal {
       public void say() {
          System.out.println("Animal");   
       }
    }
    

    Dog.java

    class Dog extends Animal {
        
        @Override
        public void say() {
            System.out.println("Dog");
        }
        
        public static void main(String args[]){
            Animal obj = new Dog();
            obj. say(); // Dog
        }
    }
    

    Cat.java

    public class Cat extends Animal {
    
        @Override
        public void say() {
            System.out.println("Cat");
        }
        
        public static void main(String args[]){
            Animal obj = new Cat();
            obj. say(); // Cat
        }
    }
    

    通过代码我们可能得到,虽然Cat和Dog对象都是调用的say()方法,但是表现却有很大差异。

  • 编译时多态---方法重载为代表

    class AddClass
    {
        void add(int a) {
           System.out.println ("a: " + a);
        }
        
        void add(int a, int b) {
           System.out.println ("a and b: " + a + "," + b);
        }
        
        double add(double a) {
           System.out.println("double a: " + a);
           return a*a;
        }
    }
    
    class PolyDemo
    {
        public static void main (String args [])
        {
            AddClass obj = new AddClass();
            obj.add(10);      // a: 10
            obj.add(10, 20);  // a and b: 10,20
            double result = obj.add(5.5); // double a: 5.5
            System.out.println("result : " + result); // result : 30.25
        }
    }
    

    通过代码我们可能得到,虽然obj都是调用的add()方法,但是表现却有很大差异。

扩展:抽象类的抽象方法和接口也是实现多态的两种方式,会在接下来的章节详细介绍。

抽象类(Abstract Class)

abstract关键字修饰的类就是抽象类(Abstract Class),抽象类里面可以有抽象方法(没有方法体)也可以有普通方法。但是普通类里面不能定义抽象方法。

注意:抽象类不能被实例化,就是说不能被用来创建对象。

  • 抽象类应用场景?

    想象一下这种情况,有一个动物类,有一个发声(sound)的方法,动物类有一些子类,例如猫类,狗类,狮子类,老虎类等等,这些子类也需要有覆写发声(sound)的方法,但是动物类没有一个通用的发声(sound)需要,所以父类的sound其实是不需要执行任何操作的。

    所以抽象类的场景就是父类有方法不需要任何操作,但是子类必须覆写这个方法

```
abstract class Animal {
    abstract void sound(); // 这个方法让子类去自己实现
    
    void anothermethod() { // 普通的方法
        System.out.println("another method");
    }
}

class Dog extends Animal {
    @Override
    void sound() { // 必须实现这个方法
        System.out.println("wang wang wang");
    }
}
```
  • 抽象类的规则

    1. 如果父类有方法不需要自己实现,就可以把父类申明为抽象类(abstract class)。继承抽象类的子类必须实现未实现的抽象方法。
    2. 抽象类不能被实例化,如果要使用必须用一个子类继承抽象类并实现抽象方法,然后用子类对象调用相应的方法。
    3. 抽象类的子类如果实现抽象类的所有方法,那子类也必须定义为抽象类,子类也不能被实例化。

    抽象类为什么不能实例化?因为抽象类不是完整的类,有的方法没有实现。如果允许抽象类实例化,那么对象调用未实现的方法就会出现问题。可以理解抽象类为一个模板,必须继承抽象类然后再使用。

接口(Interface)

接口和类相似但是又不是一个类,接口的方法都不能有实现(完全抽象),接口的属性访问修饰是public,static,final

  • 定义

    接口定义用interface关键字,interface里面的所有方法都不需要实现。

    interface MyInterface {
        // 属性只能是常量,且必须是 public static final 修饰
        int number = 1; // 等价于 public static final int number = 1;
        
        // 接口所有的方法都是抽象的
        public void func1();
        public void func2();
    }
    

    接口的使用是用 类 implements 接口, 需要实现接口的所有方法。

    class InterfaceDemo implements MyInterface {
    
        @Override
        public void func1() {
            System.out.println("implementation of func1");
        }
    
        @Override
        public void func2() {
            System.out.println("implementation of func2");
        }
        
    }
    
  • 接口继承

    接口里的方法没有实现,所以不能实现另外(implements)一个接口,但是可以继承(extends)另外一个接口。

    interface Interface1 {
        public void func1();
    }
    
    interface Interface2 extends Interface1 { // 接口继承
        public void func2();
    }
    
    class InterfaceDemo implements Interface2 {
    
        @Override
        public void func1() {
            System.out.println("func1");
        }
    
        @Override
        public void func2() {
            System.out.println("func2");
        }
        
    }
    
  • 标记接口(Marker Interface)

    标记接口有时也叫标签接口(Tag interface),即接口不包含任何方法. JDK里的Serializable接口就是一个标记接口. 和名字对应,标记接口的作用就是标识某个类型或者元数据。

    public interface Serializable {
    }
    
  • 嵌套接口

    接口可以定义在接口或者类的内部,这些接口被称为嵌套接口,或者也可以称为内部接口。例如Java的Map接口内部定义的Entry接口,我们可以用Map.Entry去访问这个接口,而不能直接用Entry去访问它。

  • 接口的一些关键点总结

    1. 接口不能初始化对象
    2. 接口的方法都是抽象方法,接口的属性都是 public static final且必须赋值
    3. 接口的使用是实现implements
    4. 接口的方法都是abstract public,所以实现的类前面都必须加上public
    5. 类必须实现接口的所有方法,否则只能定义为抽象类
    6. 接口不能定位为private和protected
    7. 接口可以继承其他的接口,但是不能实现其他接口
    8. 一个类可以实现任意数量的接口
    9. 如果有多个接口里面有相同的方法,只需要实现一个即可。
    10. 如果多个接口里面有方法名和参数一致但是返回值不一样的情况是不能允许的。
    11. 如果多个接口有相同的属性名,可以通过接口.属性加以区分。例如A.x 和 B.x
  • 接口和抽象类的区别

    抽象类 接口
    抽象类只能继承其他的类或者抽象类 接口只能继承接口
    抽象类可以有个抽象方法和正常方法 接口都是抽象方法
    定义抽象方法前面的abstract是必须的 接口都是abstract方法,所以abstract可写可不写
    抽象类的属性可以是protected和public的访问权限 接口都是public的访问权限
    抽象类的属性的权限没有限制 接口属性是public static final

封装(Encapsulation)

封装包括隐藏数据和功能的具体实现,是将该类中所有对象的属性和行为隐藏起来,并为其他对象提供一些访问的方法。例如:把属性定义为私有的,定义一些外部类能访问的方法对私有属性进行操作;再例如把Person类的speak功能做为一个方法,外部直接调用speak功能。

public class Person {
    // 一些私有的属性,外部类不能访问
    private int age;
    private String name;
    
    // 定义一些外部类能访问的方法,对私有属性进行操作
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void speak() {
        System.out.println(this.name);
    }
    
    public static void main(String[] args) {
        
        Person person = new Person();
        person.setName("Johnny");
        person.setAge(18);
        System.out.println(person.name);
        System.out.println(person.age);
        
        person.speak();
    }
    
}
  • 封装的好处

    1. 可以隐藏内部属性,且可以把属性变为只读(只有get方法)或者只写(只有set方法)或者读写皆可(有set和get方法)。
    2. 外部类可以不用关心内部实现,只需要调用就行。
    3. 增加代码的可复用性,因为方法可以在任何时间被修改,外部不需要关心,只要调用同样的一个方法可以实现多种功能。
  • 包(Package)

    Java中的包(Package)用来组织类和接口。系统定义了一些包,开发者也可以自定义自己的包。以java.util.Random; 为例: java是一个顶级包,util是一个工具类包,而Random是util包下面的一个类。

    • 包的优势

      1. 代码复用---项目中可能有很多重复的功能需要实现,这样就可以把这些功能用一个包组织起来,当需要使用的时候直接引用包就可以了。
      2. 代码结构更清晰---可以用包把各个功能模块区分出来,每个包负责相应的功能,这样代码结构更清晰,且使用起来更有效率。
      3. 解决命名冲突---在不同的包里面可以有相同的类,这样就解决了命名冲突。
    • 包的使用方式

      新建一个名称为com.johnny.Calculate的包(package),然后在这个包里面新建一个Calculator类, Calculator类里面添加一个add方法。

      package com.johnny.Calculate;
      
      public class Calculator {
          
          public int add(int num1, int num2) {
              return num1 + num2;
          }
      }
      

      说明:类的顶部需要有包的申明。package com.johnny.Calculate;

      使用方法一: 不引用包,直接用包.类来使用类。

      package com.johnny;
      
      public class Math {
          public static void main(String[] args) {
              // 直接用包名.类名 不需要import包
              com.johnny.Calculate.Calculator calculator = new com.johnny.Calculate.Calculator();
              int result = calculator.add(10, 20);
              System.out.println(result); // 30
          }
      }
      

      使用方法二:在com.johnny包的Math类中引入包import com.johnny.Calculate.Calculator;, 然后就可以直接使用Calculator类。

      package com.johnny;
      import com.johnny.Calculate.Calculator;
      
      public class Math {
          public static void main(String[] args) {
              Calculator calculator = new Calculator();
              int result = calculator.add(10, 20);
              System.out.println(result); // 30
          }
      }
      
      1. 包的申明package com.johnny;必须写在引入包import com.johnny.Calculate.Calculator;的前面
      2. import的作用就是为了减少使用类或者接口时前面需要带上一长串的包名,让代码的书写更加的简洁和易读。类似于不用import时使用一个类不许用全名,而import后只需要叫名就可以了。
    • 子包

      一个包在另外一个包里面,就把这个包称为子包。再以import java.util.Random;为例:util包就是java包的子包,上面例子com.johnny.Calculate, Calculate包就是johnny包的子包,johnny包是com包的子包。包的层级用"."连接。

- **static引包**      
    
    我们普通引包主要是为了复用其他包的public的类和接口,而static引包是为了使用其他包定义的静态的属性和方法,而不需要在属性和方法前面加上类名。
    `java.lang.Math`包中有很多数学计算的静态方法,例如sqrt,tan等,正常的引用方法如下:
    
    ```
    // 普通引包-- import包名
    import java.lang.Math;
    
    public class Demo {
        
        public static void main(String[] args) {
            // 如果是普通引包,使用类.静态方法和类.静态属性
            double number1 = Math.sqrt(4);
            System.out.println(number1); // 2.0
        }
        
    }
    ```
    
    ```
    // 静态引包--在import 和 包名.类名 中间加上 static 关键字
    import static java.lang.Math.*;
    
    public class Demo {
        
        public static void main(String[] args) {
            // 如果是静态引包,可以直接使用静态方法和静态属性
            double number1 = sqrt(4);
            System.out.println(number1); // 2.0
        }
        
    }
    ```
    
    > 说明:当使用类的静态属性或者静态方法比较频繁时候比较实用,例如需要频繁进行数学计算方法时建议用static引入。静态引入的缺点是对包的信息不了解,容易造成冲突和混淆。
    
- ** 关于包的两点说明**

    1. 多个包中可能出现命名冲突,此时使用时需要用**包名.类名**。
        例如A包里面有一个Person类,B包里面也有一个Person类。此时不能申明引入两个Person类,因为有命名冲突。
        
        ```
        import A.Person;
        import B.Person; // 会有编译错误,因为命名冲突了
        ```
        
        可以用通配符引入的方式即引入包下的所有类去解决,使用时候需要指明包名。
        
        ```
        import A.*;
        import B.*;
        
        public class PackageDemo {
            
            public static void main(String[] args) {
                
                A.Person personA = new A.Person();
                B.Person personB = new B.Person();
                
            }
        }
        ```
        
    2. 用通配符引入的时候需要特别注意,假设有个A包,A包下面有Person类,A包下面还有一个B包,B包下面有Dog,Cat两个类。如果用 `import A.*;` 此时只引入了Person类,并未引入了B包下的Dog和Cat类。如果使用`import A.B.*;`, 此时引入的是Dog和Cat类。如果要把三个类都引入需要使用`import A.*; import A.B.*;`
  • 访问修饰符

    Java中的访问修饰符有四种,public,protected, default, private,对应的访问权限如下;

    访问修饰符 同类 同包不同类(不含子类) 同包子类 不同包子类 不同包不同类(不含子类)
    public YES YES YES YES YES
    protected YES YES YES YES NO
    default YES YES YES NO NO
    private YES NO NO NO NO

final

final关键字能修饰属性,方法和类,接下来进行详细介绍。

  • final修饰变量

    final修饰的变量可以被认为是常量, 变量一旦赋值后不能修改。

    public class Person {
        final String name = "Johnny"; //申明时赋值
        
        public void changeName() {
            name = "Liu"; //此时编译器会报错
        }
    }
    

    final修饰的变量可以不在申明时候定义,此时必须在构造函数中进行赋值,且以后不能修改。用于定义不变的的一些变量。

    public class Person {
        
        final String name;
        
        public Person(String name) {
            this.name = name;
        }
        
    }
    

    如果final申明的static变量在申明时未赋值,则必须在static代码块中赋值.

    public class Person {
        
        final static String name;
        
        static {
            name = "Johnny";
        }
        
    }
    
    
  • final修饰方法

    final修饰的方法是不能被子类重写的。否则会编译错误。

    说明点:构造函数不能定义为final;

  • final修饰类

    final修饰的类是不能被子类化的,也就是说其他类不能继承final类。Java中的String,Integer 等类都是final类。
    系统提供的final类 --- enum

    在其他语言中,枚举(enum)一般是一系列整型或者字符串集合,但是Java中的枚举其实是一个final类,可以表示一系列的数据的集合,也能有方法和变量。

    用枚举定义常量

    enum WeekDay {  
        Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday;
    }
    
    System.out.println(WeekDay.Monday);  // Monday 注意此时不是字符串
    

    枚举可以添加变量和方法

    enum PersonEnum {   
        Johnny(18), Lan(17), Wen(1);
        
        private int age;
        
        // 定义了属性必须要有构造方法,且必须为私有方法
        private PersonEnum(int age) {
            this.setAge(age);
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    枚举可以实现接口

    
    public interface EnumInterface {
        void speak();
    }
    
    // 实现接口
    enum InterfaceEnum implements EnumInterface {
        A, B;
        
        @Override
        public void speak() {
            System.out.println(this);
        }
        
    }
    
    // 使用
    InterfaceEnum.A.speak(); //A
    
    

    说明:枚举可以使用在switch语句中。

你可能感兴趣的:(Java面向对象详解)