【JavaSE】面向对象编程

工作中要用到无穷多的类,这些类很可能会出现重名的情况。这时候怎么办?Java用“包”机制来解决这个问题

包(package)提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。

只有类名并不代表具体的类,而应该是**“包名+类名”才能对应到具体的某个类**(包名+类名才是完整的类名)

位于包中的类,在文件系统中也必须有包名层次相同的目录结构

导入包中的类

想使用别人写好的类,有三种方式

  1. 引用前写包的全称,并用 . 分割(写明完整的类名)

    public class TestDemo {
        public static void main(String[] args) {
    	//java.util是包名,Date是用到的类名
            java.util.Date date = new java.util.Date();
            // 得到一个毫秒级别的时间戳
            System.out.println(date.getTime());
        }
    }
    
  2. 用import + 包名 + 类名(导入包中指定的那个类),再直接引用

    import java.util.Date; 
    
    public class TestDemo {
        public static void main(String[] args) {
            Date date = new Date();
            // 得到一个毫秒级别的时间戳
            System.out.println(date.getTime());
        }
    }
    
  3. 用import + 包名 + .*(导入包中所有的类,.*称为通配符) 再直接引用。这时也可以同时调用该包里的其他类

    import java.util.*;
    
    public class TestDemo {
        public static void main(String[] args) {
            Date date = new Date();
            // 得到一个毫秒级别的时间戳
            System.out.println(date.getTime());
        }
    }
    

    注意:如果导入了两个包,两个包里有重名的类名,则调用这个类是会产生冲突,这时必须在类前面加上全部的包名。

    import java.util.*;
    import java.sql.*;
    public class TestDemo {
        public static void main(String[] args) {
            // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
            Date date = new Date();
            System.out.println(date.getTime());
        }
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hTUxG6wc-1637722022239)(assets/image-20211119121858-m2jgfci.png)]
    应该这样写:

    import java.util.*;
    import java.sql.*;
    public class TestDemo {
        public static void main(String[] args) {
    	java.util.Date date = new java.util.Date();
            System.out.println(date.getTime());
        }
    }
    

静态导入(import static)

  1. import static 可以导入包中的静态方法和字段(很少用)
    import static java.lang.Math.*;
    public class TestDemo {
        public static void main(String[] args) {
            double x = 30;
            double y = 40;
            // 静态导入的方式写起来更方便一些.
            // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
            double result = sqrt(pow(x, 2) + pow(y, 2));
            System.out.println(result);
        }
    }
    

import与package

import 和 package的区别

  1. import
    1. import 是导入一个具体的类(import java.util.Arrays),不能导入一个包
    2. import java.util.*表示导入util包下所有的类。但并不是一下全部导入,需要哪个类,才会拿哪个类。
      1. 而C语言是全部导入
      2. 但如果导入多个包时,要注意不同包里可能有同名的类。这时要写包的全名(如util和sql都含有Data的类)
  2. 一旦Java源文件中用了package,则意味着该源文件里定义的所有的类都属于这个包
    1. package java.util
    2. 只有类名并不代表具体的类,而应该是“包名+类名”才能对应到具体的某个类

把类导入到包中(用package)

位于包中的类,在文件系统中也必须有包名层次相同的目录结构

  1. 创建一个包
    1. 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
    2. 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 )
    3. 包名要和代码路径相匹配。例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存****储代码**.**
    4. 如果一个类没有 package 语句, 则该类被放到一个默认包中
  2. 在IDEA中创建一个包
    1. 在 IDEA 中先新建一个包: 右键src -> 新建 -> 包
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPz1ZiyG-1637722022240)(assets/image-20211119133257-onuicor.png)]
    2. 在弹出的对话框中输入包名, 例如com.bit.demo
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FKBLzQRD-1637722022241)(assets/image-20211119133924-4ugeb8p.png)]
    3. 在包中创建类, 右键包名 -> 新建 -> 类, 然后输入类名即可
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcDykgWT-1637722022241)(assets/image-20211119134024-ouh5tnn.png)]
    4. 此时目录结构已经被IDEA创建出来了
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y3HAhHdt-1637722022242)(assets/image-20211119134127-7wy4d7p.png)]
    5. 同时在新建的.java文件的最上方,也出现了package语句,表明该文件下的所有类,都在这个包里。
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9MJ7QMqg-1637722022242)(assets/image-20211119134248-sebv80c.png)]
  3. #TODO#如果自己写的类名与导入的包中类名重名该怎么办?

访问控制符

Java有三个访问控制符:private、protected和public。对应三个访问控制级别。

另外还有一个不加任何访问控制符的访问控制级别(默认default)

访问控制级别由小到大:private -> default -> protected -> public。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ic3iFWLo-1637722022243)(assets/image-20211119153557-6cbqkbc.png)]

如果一个方法能用 private, 就尽量不要用public

这四种权限可以分别与static关键字搭配,构成静态的变量。

下面分别介绍这四种访问控制权限

private(当前访问权限)

如果类里的一个成员(包括成员变量、方法和构造方法等)被private修饰,则该成员只能在当前类中被使用。

default(包访问权限)

如果类里的一个成员或者一个外部类不适用任何访问控制符修饰,那就称它是包访问权限的。这个成员变脸只能在被包内的其他类使用,不能被包外部的类使用。

实例:

  1. 包的目录结构如下(test.java和test2.java位于同一个包内,而TestDemo.java不在此包内)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e4pSleIF-1637722022243)(assets/image-20211119135604-82wue6k.png)]
  2. test.java
    package com.bit.demo1;
    
    public class test {
        int value1 = 1;
        private int value2 = 2;
    }
    
  3. test2.java
    package com.bit.demo1;
    
    public class test2 {
        public static void main(String[] args) {
            test demo = new test();
            System.out.println(demo.value1);
        }
    }
    
    // 执行结果, 能够访问到 value 变量
    10
    
  4. TestDemo.java
    不再同一个包里,即便用import导入了此包,也不能调用test类里默认范围的变量
    import com.bit.demo1.test;
    
    public class TestDemo {
        public static void main(String[] args) {
            test demo = new test();
            System.out.println(demo.value1);
        }
    }
    
    //编译出错
    java: value1在com.bit.demo1.test中不是公共的; 无法从外部程序包中对其进行访问
    
protected(子类访问权限)

如果一个成员被protected访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。

实例:

  1. demo1和demo是两个不再一个包中的类
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zz5eq53-1637722022244)(assets/image-20211119213140-xn6jppf.png)]
  2. demo.java
    package com.bit.demo2;
    
    public class demo {
        protected int val1 = 10;
        protected static int val2 = 20;
    }
    
    
  3. demo1.java
    package com.bit.demo1;
    import com.bit.demo2.*;
    
    public class demo1 extends demo{
        public void func() {
            System.out.println("val1 = " + this.val1);
        }
    
        public static void main(String[] args) {
            demo a = new demo();
            System.out.println("val2 = " + a.val2);
        }
    }
    
  4. 课件protected修饰的变量可以被不同包的子类引用
    1. 但是要注意:static方法(如main方法)不能引用普通成员变量

通常情况下,用protected修饰一个方法,是希望其子类能重写这个方法。

public(公共访问权限)

这是最宽松的一个访问控制级别。如果一个成员变量或外部类被public修饰,则这个成员变量或外部类就可以被所有类访问,不管访问类和被访问类是否在同一个包中,是否具有父子继承关系。

常见的系统包

1. java.lang:系统常用基础类(StringObject),此包从JDK1.1后自动导入(因此不用import也能直接调用String、System类)。
2. java.lang.reflect:java 反射编程包;
3. java.net:进行网络编程开发包。
4. java.sql:进行数据库开发的支持包。
5. java.util:java提供的工具程序包。(集合类等) 非常重要
6. java.io:I/O编程开发包

继承

对类中的共性进行抽取。使用extends关键字进行处理。可以对代码进行重复使用

下面是一个实例, Animal 和 Cat 以及 Bird 这两个类中存在一定的关联关系。因此可以让Cat和Bird继承Animal,来达到代码重用的效果。

此时Animal被称为:父类、基类或超类

Cat,Bird被称为:子类、派生类。

class Animals {
    public String name;
    public int age;

    public Animals(String myname, int myage) {
        this.name = myname;
        this.age = myage;
    }

    public void eat() {
        System.out.println(name + ", " + age + " 岁 " + "正在吃饭");
    }
}

class Cat extends Animals {
    public Cat (String myname, int myage) {
        super(myname, myage); //调用父类的构造方法
    }
}

class Bird extends Animals {
    public String color;
    public Bird (String myname, int myage, String color) {
        super(myname, myage);
        this.color = color;
    }

    public void fly() {
        System.out.println("this " + color + "Bird, named " + name + ", is flying!");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Cat cat1 = new Cat("小白", 5);
        Bird bird1 = new Bird("八哥", 8, "Red");
        cat1.eat();
        bird1.eat();
        bird1.fly();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2a5Iibu-1637722022244)(assets/image-20211119142719-ff2udbt.png)]

语法规则

  1. 基本格式
    class 子类 extends 父类 {
    
    }
    
  2. 注意:
    1. Java 中一个子类只能有一个直接父类 (而C++/Python等语言支持多继承**)**,但是可以有多个间接父类(多层继承)
    2. 子类会继承父类的所有 public 的字段、方法和内部类(包括内部接口、枚举),但不能获得父类的构造方法和初始化块
      1. 子类要先写构造方法(用super()进行“显式调用父类的构造方法”)
      2. 如果子类没有写构造方法,则会默认调用父类的构造函数
    3. 对于父类的 private 的字段和方法, 子类中是无法访问的(调用父类的构造方法不影响)
      1. 例如把上面例子中Animal父类中的age改为private修饰。编译通过。这是因为子类中除了构造方法,没有别的方法调用了age。
      2. 但如果把name改为private修饰,编译就会报错。因为Bird子类中,fly调用了name,这是不被允许的。
        class Bird extends Animal {
            public Bird(String name) {
                super(name);
            }
            public void fly() {
                System.out.println("this " + color + "Bird, named " + name + ", is flying!");
            }
        }
        // 编译出错
        Error:(19, 32) java: name 在 Animal 中是 private 访问控制
        
    4. 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用。(例如构造函数的引用)
    5. 子类和父类有同名的成员变量时,会优先使用自己的。如果想用父类的,则需要用super.data
    6. 可以有多层继承,但是建议最多不超过三层

final关键字

  1. 修饰一个变量或者字段的时候, 表示常量 (不能修改)
    final int a = 10; 则a是常量,不能被修改

  2. 修饰类的时候,表示被修饰的类不能被继承
    final class A; 代表整个类不可以被继承

    final public class Animal {
    ...
    }
    public class Bird extends Animal {
    ...
    }
    // 编译出错
    Error:(3, 27) java: 无法从最终com.bit.Animal进行继承
    

    1

  3. #TODO#修饰方法:后面再说

组合

与继承类似,组合也是表达类之间关系的一种方式,也能达到代码重用的效果。

public class Student {
...
}
public class Teacher {
...
}
public class School {
    public Student[] students;
    public Teacher[] teachers;
}

继承要表达的是一种“是(is - a)”的关系,组合表达的是“有(has - a)”的关系

多态

向上转型

  1. 父类引用 引用 子类对象
    package com.bit.demo3;
    
    class Animal {
        public String name;
        public int age;
    
        public Animal(String myname, int myage) {
            this.name = myname;
            this.age = myage;
        }
    
        public void eat() {
            System.out.println(name + ", " + age + " 岁 " + "正在吃饭");
        }
    }
    
    class Cat extends Animal {
        public Cat (String myname, int myage) {
            super(myname, myage); //调用父类的构造方法
        }
    }
    
    class Bird extends Animal {
        public String color;
        public Bird (String myname, int myage, String color) {
            super(myname, myage);
            this.color = color;
        }
    
        public void fly() {
            //System.out.println("this " + color + "Bird, named " + name + ", is flying!");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Cat("小黑", 10); 
            //向上转型(父类引用 引用子类类型)
        }
    }
    
  2. 发生时机:
    1. 直接赋值
          public static void main(String[] args) {
              Animal animal = new Cat("小黑", 10); 
              //直接赋值
          }
      
    2. 方法传参
      public class Test {
          //方法传参
          public static void func(Animal animal) {
      	//接收是Animal父类
              animal.eat();
          }
      
          public static void main(String[] args) {
              Cat cat = new Cat("花花", 8);
              func(cat); //传过去的是Cat子类
          }
      }
      
    3. 方法返回
      public class Test {
          //方法返回
          public static Animal findMyAnimal() {
              Bird bird = new Bird("圆圆", 5, "Green")return bird; //传回去的是Bird子类
          }
      
          public static void main(String[] args) {
              Animal animal = findMyAnimal(); //接受是Animal父类
          }
      }
      

动态绑定(运行时绑定)

当子类和父类出现同名方法的时候,就会发生动态绑定。

对于子类实现父类的同名方法,并且参数类型和个数完全相同,也称为方法重写/覆盖(override)(重写具体见下一小节)

代码实例(三个文件在一个包里):

  1. Animal.java
    package com.bit.Test1;
    
    public class Animal {
        protected String name;
        public Animal (String name) {
            this.name = name;
        }
        public void eat(String food) {
            System.out.println("我是一只小动物");
            System.out.println(this.name + "正在吃" + food);
        }
    }
    
  2. Bird.java
    package com.bit.Test1;
    
    public class Bird extends Animal {
        public Bird(String name) {
            super(name);
        }
    
        @Override
        public void eat(String food) {
            System.out.println("我是一只小鸟");
            System.out.println(this.name + "我正在吃" + food);
        }
    
        public void fly () {
            System.out.println("我要的飞翔");
        }
    }
    
  3. Test.java
    package com.bit.Test1;
    
    public class Test {
        public static void main(String[] args) {
            Animal animal = new Animal("小黑");
            animal.eat("肉");
            Animal bird = new Bird("圆圆");
            bird.eat("玉米");
        }
    }
    
  4. 可以看到animal 和 bird 都是Animal类型,但是animal指向Animal实例,bird指向Bird实例。
  5. 两个对象都调用eat方法是,animal调用了父类的方法,bird则调用了Bird子类的方法。

动态绑定注意:

  1. 父类引用 引用 子类的对象
  2. 通过这个父类引用 调用父类 和 子类 同名的覆盖方法(就是重写)
  3. 为什么叫“动态绑定”(也叫运行时绑定
    1. 编译的时候不能确定 此时调用的谁的方法
    2. 在运行的时候决定的 -> 动态绑定
  4. 与之对应的还有静态绑定(也叫编译时绑定
    1. 根据你给的参数的类型+个数,推导出你调用的哪个函数。
    2. 常见的是一个构造方法被重载时,根据传参推断你调用的哪个构造函数。
    3. 这个过程时编译的时候就决定的 -> 静态绑定
  5. 通过父类引用,只能访问父类自己的成员
    1. 上面的例子中,bird依然是Animal(父类)实例,不能调用Bird子类中的fly()方法。

              bird.fly();
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ipBexMDX-1637722022244)(assets/image-20211120103331-4crmiwc.png)]

    2. 同样,如果在上面的代码上,给Bird加了一个新的成员变量,然后尝试用bird调用这个变量,则会编译错误

方法重写

  1. 重写要求
    1. 方法名相同
    2. 参数列表相同(个数+类型)
    3. 返回值(尽量)相同
    4. 父子类的情况下
  2. 注意:
    1. 以下方法不能重写
      1. static 方法不能重写
      2. 父类中 private 方法不能被重写
      3. 被 final 修饰的关键字,不能被重写
    2. 子类的访问控制符大于等于父类的访问控制符。
      1. 比如上面的例子,如果把Bird子类中的重写eat()方法从public改为private,则报错。
        // Animal.java
        public class Animal {
            public void eat(String food) {
            ...
            }
        }
        public class Bird extends Animal {
            private void eat(String food) {
        	...
            }
        }
        
        //编译错误
        java: com.bit.Test1.Bird中的eat(java.lang.String)无法覆盖com.bit.Test1.Animal中的eat(java.lang.String)
          正在尝试分配更低的访问权限; 以前为public
        
    3. 返回值其实可以不同,叫协变类型(但很少见,目前也不推荐)
  3. @override 注解
    1. 有了这个注解能帮我们进行一些合法性校验. 例如不小心将方法名字拼写错了 **(**比如写成aet), 那么此时编译器就会发现父类中没有aet 方法, 就会编译报错, 提示无法构成重写
          @Override
          //加上注解后,下面的重写方法名写错了
          public void ate(String food) { 
              System.out.println("我是一只小鸟");
              System.out.println(this.name + "我正在吃" + food);
          }
      
      //编译错误
      java: 方法不会覆盖或实现超类型的方法
      
  4. 重载和重写的区别
    二者其实没有什么联系,只是单纯的名字比较像。这里作为复习还是总结一下。
    1. 重载(overload)是在一个类中,有方法名相同,但参数的类型或数量不同的方法。且重载方法之间没有权限要求。
    2. 重写(override)是在继承关系之间,子类可以重写父类的方法,要求方法名称,参数类型及其个数完全相同(返回值类型也尽量相同)。另外子类重写方法的访问权限要大于或等于父类方法的访问权限。
  5. 重写快捷方法、
    在子类的空白处右键,点generate,然后点override methods,选择要重写的方法

向下转型

上面重写的例子中,Animal类型的bird是不能使用Bird子类的方法的。但如果还想用,则需要向下转型

public class Test {
    public static void main(String[] args) {
        Animal animal = new Bird("小黑");
        Bird bird = (Bird) animal; //向下转型
        bird.fly();
    }
}

animal 强制类型转化为Bird类型,然后赋给新定义的bird。

但最好还是少用,因为强转之前必须确定 强转对象在向上转型之前 和接收的对象是同一个类型。看下面的代码,把cat强转为Bird类型是会出现编译错误的

public class Test {
    public static void main(String[] args) {
        Animal animal = new Cat("小黑"); 
	//animal向上转型前是Cat子类,所以不能再向下转型为Bird子类
        Bird bird = (Bird) animal;
        bird.fly();
    }
}

//编译错误
Exception in thread "main" java.lang.ClassCastException: com.bit.Test1.Cat cannot be cast to com.bit.Test1.Bird
	at com.bit.Test1.Test.main(Test.java:6)

instanceof 关键字

可以判定一个引用是否是某个类的实例. 如果是, 则返回 true.

可以用instanceof判定向下转型的对象是否安全

Animal animal = new Cat("小猫");
if (animal instanceof Bird) {
	Bird bird = (Bird)animal;
	bird.fly();
}

super关键字

表示获取父类对象的引用

注意:super不能出现在静态方法中!!

  1. super(): 调用父类的构造方法
    1. super()只能放在子类的构造方法里第一行
  2. super.func();调用父类的普通方法
    1. 当子类方法中有与父类方法重名的时候,父类方法会被重写。这时如果还想调用父类方法则需要用到super.func()
    2. 不用super.func(),则子类会调用自己的方法
      //Cat.java
      public class Cat extends Animal{
          public Cat(String name) {
              super(name);
          }
          public void eat(String food) {
              System.out.println("我是一只猫");
              System.out.println(this.name + "正在吃" + food);
          }
      }
      
      //Test.java
      public class Test {
          public static void main(String[] args) {
              Cat cat = new Cat("小白");
              cat.eat("鱼");
          }
      }
      
      //输出
      我是一只猫
      小白正在吃鱼
      
    3. 使用super.func(),可以调用父类的方法
      //Cat.java
      public class Cat extends Animal{
          public Cat(String name) {
              super(name);
          }
          public void eat(String food) {
              super.eat(food);
              System.out.println("我是一只猫");
              System.out.println(this.name + "正在吃" + food);
          }
      }
      
      //Test.java
      public class Test {
          public static void main(String[] args) {
              Cat cat = new Cat("小白");
              cat.eat("鱼");
          }
      }
      
      //输出
      我是一只小动物
      小白正在吃鱼
      我是一只猫
      小白正在吃鱼
      
  3. super.data
super与this的区别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I4LQxW6D-1637722022246)(assets/image-20211120154534-5xu2fj4.png)]

理解多态

多态主要是通过向上转型、动态绑定和重写实现的(向下转型很少用)

小栗子

下面举一个小栗子,来展示一下多态的使用场景

现在想要实现一个打印各种团的功能,所以先建立了一个Shape的父类,又根据各种形状建立子类

以下部分是类的编写者要写的代码

class Shape {
    public void draw() {
        //do nothing
    }
}

class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("○");
    }
}

class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("□");
    }
}

class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("♣");
    }
}

以下代码是类的调用者要写的代码

  1. 不用多态来打印
    则需要在一个循环里进行多次 if 条件判断
    public class TestDemo {
        public static void drawMap(Shape shape) {
            shape.draw();
        }
    
        public static void main(String[] args) {
            Flower flower = new Flower();
            Rect rect = new Rect();
            Cycle cycle = new Cycle();
            Shape[] shapes = {flower, rect, cycle, rect, flower};
            for (Shape shape:shapes) {
                if (shape.equals("cycle")) {
                    cycle.draw();
                } else if (shape.equals("rect")) {
                    rect.draw();
                } else if (shape.equals("flower")) {
                    flower.draw();
                }
            }
        }
    }
    
  2. 用多态
    可以看到代码简洁了非常多。而且如果要添加图形,只需要在数组里面添加元素,就可以了。不用再添加额外的 if 条件
    public class TestDemo {
        public static void drawMap(Shape shape) {
            shape.draw();
        }
    
        public static void main(String[] args) {
            Flower flower = new Flower();
            Rect rect = new Rect();
            Cycle cycle = new Cycle();
            Shape[] shapes = {flower, rect, cycle, rect, flower};
            for (Shape shape:shapes) {
                shape.draw();
            }
        }
    }
    
作用总结
  1. 可以进一步降低类的使用者对类的使用成本。
    1. 封装是让类的调用者不需要知道类的实现细节.
    2. 多态能让类的调用者连这个类的类型是什么都不必知道,只需要知道这个对象具有某个方法即可
      比如上面的例子中,调用者想打印图案,不用具体知道圆形是Cycle子类型,方框是什么类型等等,只用知道各种形状的实例都是Shape的子类就行了,调用的时候就可以直接无脑用Shape去调用各个子类里被重写的方法。
    3. 当然这个例子还可以让代码更简洁,让Shape父类变成一个接口(下面会写)
  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else。

不要再构造方法中调用重写的方法

这是一个有坑的代码

class B {
    public B() {
        //父类的构造方法里调用了func()方法
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
	//子类对func()方法进行了重写
        System.out.println("D.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
}

// 执行结果
D.func() 0

过程:

  1. 构造对象D的时候,会调用B的构造方法
  2. B中的构造方法调用了func()方法,但由于D中对func()方法进行了重写,所以会出发动态绑定
  3. 所以B中的构造方法会调用D中的func()
  4. 而此时D对象自身还没有被构造(也就是num成员变量还没来得及被赋值)所以,这时num还在未初始化的状态,默认为0。
  5. 所以尽可能不要再构造方法里调用其他方法,否则可能会出现奇怪的问题,而且及其难以发现。

抽象类

语法规则

没有实际工作(do nothing)的方法,可以将其设计为抽象方法

包含抽象方法的类叫抽象类

abstract class Shape {
    abstract public void draw();
}

注意:

  1. 抽象方法不能有方法体(没有{}),也不能执行代码
  2. 如果一个类继承了抽象类,则必须重写抽象类的所有抽象方法。否则编译会报错。
  3. 抽象类不能直接被实例化
    Shape shape = new Shape();
    // 编译出错
    Error:(30, 23) java: Shape是抽象的; 无法实例化
    
  4. 抽象方法不能是private的(本来重写的方法就不能是private的)
    abstract class Shape {
    abstract private void draw();
    }
    // 编译出错
    Error:(4, 27) java: 非法的修饰符组合: abstractprivate
    
  5. 抽象类中可以包含非抽象方法,也可以包含成员变量。这里的非抽象方法和普通方法的规则是一样的,可以被重写,也可以被子类调用。
    abstract class Shape {
        abstract public void draw();
        void func() {
    	//抽象类中的非抽象方法
            System.out.println("func");
        }
    }
    class Rect extends Shape {
    ...
    }
    public class Test {
        public static void main(String[] args) {
            Shape shape = new Rect();
            shape.func();
        }
    }
    // 执行结果
    func
    
  6. 抽象类也可以继承抽象类
    1. 一个抽象类A,如果继承了一个抽象类B,那么这个抽象类A,可以不实现抽象父类B的抽象方法。
    2. 但当A再次被一个普通类继承后,则A和B的两个抽象类忠的抽象方法,必须被重写。
  7. 抽象类不能被final修饰(final+类就是不能被继承)
  8. #TODO#抽象方法也不能被final修饰(final+方法就是不能被重写)

抽象类的作用

  1. 抽象类的最大意义就是被继承
  2. 抽象类本身不能被实例化,必须创建该抽象类的子类。然后让子类重写抽象类中的方法。
  3. 所以为什么不直接用普通类呢?也能被继承,也能被重写
    1. 使用抽象类相当于多了一重编译器的校验
    2. 有些工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的
    3. 父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.

很多语法存在的意义都是为了 “预防出错”, 例如final、@Override等,

充分利用编译器的校验, 在实际开发中是非常有意义的

接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量

语法规则

interface Ishape {
    void draw(); //省略了abstract public
    int num = 10; //省略了public static final
}

class Cycle implements Ishape {
    @Override
    public void draw() {
        System.out.println("o");
    }
}

public class Test {
    public static void main(String[] args) {
        Ishape shape = new Cycle();
        shape.draw();
    }
}
  1. interface 定义一个接口
  2. 接口中只能包含静态常量(static final),当然public static final也可以省略
  3. 接口中的方法一定是public,因此可以省略public
  4. 接口中的抽象方法可以省略abstract
  5. 接口中的普通方法,不能有具体时间。如果非要实现,必须用 default 来修饰这个方法
  6. 接口中可以有 static 方法。
  7. Cycle 使用 implements 继承接口,其含义不是“扩展”而是“实现”
  8. 当一个类实现了一个接口,就必须要重写接口当中的抽象方法。
  9. 接口不能单独被(通过new)实例化
  10. 要注意子类中重写抽象方法的访问权限,也只能是public,且public不能省略!(省略就是包访问权限了,相当于访问权限升级了)
  11. 接口与接口之间可以通过extends来拓展接口的功能
    1. 接口B通过extends来拓展另一个接口C之间的关系时。
    2. 当一个类D通过implements实现接口B的时候,此时必须重写B和C两个接口的抽象方法。

实现多个接口

继承(extends)一次只能继承一个父类,如果想实现“多继承”,Java可以用同时实现多个接口来达到这个效果

  1. 一个类可以通过extends继承一个抽象类或者普通类,同时也可以通过implements实现多个接口,接口之间使用 , 隔开。
  2. 一个实例
    class Animal {
        protected String name;
    
        public Animal(String name) {
            this.name = name;
        }
    }
    
    //不是所有的动物都会飞,所以fly()不能写到animal当中
    //如果写到另一个类中,也不行,因为一个类不能继承多个类
    //所以用接口来实现这些功能最合适
    interface IFlying {
        void fly();
    }
    
    interface IRunning {
        void run();
    }
    
    interface ISwimming {
        void swim();
    }
    
    class Dog extends Animal implements IRunning {
        public Dog(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            System.out.println(this.name + "正在跑");
        }
    }
    
    class Frog extends Animal implements IRunning, ISwimming {
        public Frog(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            System.out.println(this.name + "正在跑");
        }
    
        @Override
        public void swim() {
            System.out.println(this.name + "正在游泳");
        }
    }
    
    class Bird extends Animal implements IFlying {
        public Bird(String name) {
            super(name);
        }
        @Override
        public void fly() {
            System.out.println(this.name + "正在飞!");
        }
    }
    
    class Robot implements IRunning {
        @Override
        public void run() {
            System.out.println("机器人在跑");
        }
    }
    
    public class Test1 {
        public static void runFunc(IRunning irunning) {
            irunning.run();
        }
    
        public static void main(String[] args) {
            runFunc(new Dog("狗子"));
            runFunc(new Frog("青蛙"));
            runFunc(new Robot()); //机器人不是Animal的子类,但也可以调用runFunc函数
        }
    }
    

常用接口

比较的时候,用哪个接口取决于业务需求,但一般推荐比较器接口(Comparator)

  1. Comparable
    1. 作用
      对自定义的数据类型要进行大小比较
    2. 缺点
      对类的侵入性非常强。一旦写好了,就不敢轻易改动
    3. 实例
    import java.util.Arrays;
    
    class Student implements Comparable<Student> {
        public int age;
        public String name;
        public double score;
    
        public Student(int age, String name, double score) {
            this.age = age;
            this.name = name;
            this.score = score;
        }
    
        @Override
        //谁调用compareTo谁就是this
        public int compareTo(Student o) {
            /*
            if (this.age > o.age) {
                return 1;
            } else if (this.age == o.age) {
                return 0;
            } else {
                return -1;
            }
             */
    	//return this.name.compareTo(o.name);
            return this.age - o.age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "age=" + age +
                    ", name='" + name + '\'' +
                    ", score=" + score +
                    '}';
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Student[] students = new Student[3];
            students[0] = new Student(12, "zhangsan", 91.5);
            students[1] = new Student(13, "wangwu", 90.5);
            students[2] = new Student(11, "lisi", 89.3);
            System.out.println(Arrays.toString(students));
            Arrays.sort(students);
            System.out.println(Arrays.toString(students));
        }
    
        public static void main1(String[] args) {
            int[] array = {1, 21, 3, 14, 5, 16};
            System.out.println(Arrays.toString(array));
            Arrays.sort(array);
            System.out.println(Arrays.toString(array));
        }
    }
    
  2. Comparator

    1. 特点
      对类的侵入性很小,而且很灵活

    2. 实例
      还是刚才的例子,但是要单独写一个排序的类
      如果需要按年龄排序,则写一个AgeComparator,

      class AgeComparator implements Comparator<Student> {
          @Override
          public int compare(Student o1, Student o2) {
              return o1.age - o2.age;
          }
      }
      

      如果要根据成绩排序,则写一个ScoreComparator

      class ScoreComparator implements Comparator<Student> {
          @Override
          public int compare(Student o1, Student o2) {
              return (int)(o1.score - o2.score);
          }
      }
      

      如果根据姓名排序,则写一个NameComparator

      class NameComparator implements Comparator<Student> {
          @Override
          public int compare(Student o1, Student o2) {
              return o1.name.compareTo(o2.name);
          }
      }
      

      然后就可以按照你想要的排序方式去比较

          public static void main(String[] args) {
              Student[] students = new Student[3];
              students[0] = new Student(12, "zhangsan", 91.5);
              students[1] = new Student(13, "wangwu", 90.5);
              students[2] = new Student(11, "lisi", 89.3);
              System.out.println(Arrays.toString(students));
      
              //按年龄排序
              AgeComparator ageComparator = new AgeComparator();
              Arrays.sort(students, ageComparator);
              System.out.println(Arrays.toString(students));
      
              //按成绩排序
              ScoreComparator scoreComparator = new ScoreComparator();
              Arrays.sort(students, scoreComparator);
              System.out.println(Arrays.toString(students));
      
              //按姓名排序
              NameComparator nameComparator = new NameComparator();
              Arrays.sort(students, scoreComparator);
              System.out.println(Arrays.toString(students));
         }
      
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值