JavaSE之面向对象编程(1)

目录

一、包

1.什么是包

2.包的作用

2.导入包中的类

3.静态导入

4.将类放在包中

5.常见的系统包

6.import和package的区别

二、继承

1.概念

2.语法规则

3.super关键字

 4.protected关键字

5.更复杂的继承关系

6.final关键字

三、组合

四、多态

1.向上转型

2.动态绑定与方法重写

3.向下转型 

4.理解多态


一、包

1.什么是包

(package) 是组织类的一种方式,用于区别类名的命名空间。

2.包的作用

  • 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

  • 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

  • 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

2.导入包中的类

Java 中已经提供了很多现成的类供我们使用,如:
public class Test {
    public static void main(String[] args) {
        java.util.Date date = new java.util.Date();// 得到一个毫秒级别的时间戳
                                                   //使用 java.util.Date 这种方式引入 java.util 这个包中的 Date 类
        System.out.println(date.getTime());
   }
}

但是这样写比较麻烦,我们可以使用import语句(在 Java 中,如果给出一个完整的限定名,包括包名、类名,那么 Java 编译器就可以很容易地定位到源代码或者类。import 语句就是用来提供一个合理的路径,使得编译器可以找到某个类。例如,下面的命令行将会命令编译器载入 java_installation/java/io 路径下的所有类,import java.io.*;)导入包,如:

import java.util.Date;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date.getTime());
   }
}
如果需要使用 java.util 中的其他类 , 可以使用 import java.util.*
import java.util.*;
public class Test {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date.getTime());
   }
}

*是一个通配符,导入这个包底下所有的类。但是它不是一下子全部导入。Java处理的时候,需要谁才会拿谁。但是在C语言里面,通过include关键字,导入后就会把头文件里面的内容全部都拿过来。但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况,如:

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

3.静态导入

使用 import static 可以导入包中的静态的方法和字段,如:
import static java.lang.System.*;
public class Test {
    public static void main(String[] args) {
        out.println("hello");
   }
}

利用静态导入,有时候写代码可以方便一些:

import static java.lang.Math.*;
public class Test {
    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);
   }
}

4.将类放在包中

规则:

(1)在文件的最上方加上一个 package 语句指定该代码在哪个包中 .
(2)包名需要尽量指定成唯一的名字 , 通常会用公司的域名的颠倒形式 ( 例如 com.bit.demo1 )

(3)包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.

(4)如果一个类没有 package 语句, 则该类被放到一个默认包中

注意:包名要是小写的英文字母

5.常见的系统包

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

6.import和package的区别

import:“引入”,引入类中需要的类

package:“包”,指类所在的包

二、继承

1.概念

代码中创建的类 , 主要是为了抽象现实中的一些事物 ( 包含属性和方法 ). 有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联,如:我们设计一个类表示动物
class Dog{
   public String name;
   public int age;

   public void eat(){
      System.out.println("eat()");
   }
}
class Bird{
   public String name;
   public int age;
   public String wing;

   public void eat(){
      System.out.println("eat()");
   }
   punlic void fly(){
      System.out.println("fly()");
   } 
}
这个代码我们发现其中存在了大量的冗余代码,
仔细分析,我们发现:
(1)这两个类都具备一个相同的 eat 方法 , 而且行为是完全一样的 .
(2)这两个类都具备一个相同的 name 属性,一个相同的age属性 , 而且意义是完全一样的 .
从逻辑上讲 ,Dog  Bird 都是一种 Animal (is - a 语义 ). 此时我们就可以让 Cat Bird 分别继承 Animal , 来达到代码重用的效果
此时 , Animal 这样被继承的类 , 我们称为 父类 , 基类 超类 , 对于像  Dog  Bird 这样的类 , 我们称为 子类 , 派生类

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为 

2.语法规则

class 子类 extends 父类 {
     ......
}
(1)使用 extends 指定父类 .
(2)Java 中一个子类只能继承一个父类 ( C++/Python 等语言支持多继承 ).
(3)子类会继承父类的所有 public 的字段和方法,子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
(4)对于父类的 private 的字段和方法 , 子类中是无法访问的 .
(5)子类的实例中 , 也包含着父类的实例 . 可以使用 super 关键字得到父类实例的引用
(6)提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)
(7)当一个类没有继承的两个关键字,则默认继承object(这个类在  java.lang 包中,所以不需要  import)祖先类

现在,我们可以对上面的代码,进行优化:

class Animal{
   public String name;
   public int age;

   public void eat(){
      System.out.println("eat()");
   }
}
class Dog extends Animal{

}
class Bird extends Animal{
   public String wing;

   punlic void fly(){
      System.out.println("fly()");
   } 
}

3.super关键字

(1)super();//调用父类的构造方法
(2)super.func();//调用父类的成员方法
(3)super.data;//调用父类的成员属性
注意:
(1)它们都不能出现在静态方法中,它们是父类对象的引用,它们是依赖对象的,而静态方法不依赖于对象
(2)子类构造时要先调用父类的构造方法(因为构造一个对象,先调用其构造方法,来初始化其成员函数和成员变量。子类拥有父的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化)

【子类中所有的构造器都会默认调用父类中的无参构造器,因为每一个子类构造器内的第一行都有一条隐式的super();若父类中没有无参构造器,那么子类的构造器内必须通过super语句指定要调用的父类中的构造器】

class Animal{
   public String name;
   public int age;
   private int count;

   public Animal(String name,int age){
      this.name = name;
      this.age = age;
   }

   public void eat(){
      System.out.println("eat()");
   }
}
class Dog extends Animal{
    //public Dog(){
    //   super();
    //}  当代码中一个构造函数都没有,就会默认调用这个构造函数
    public Dog(String name,int age){
      super(name,age);//调用父类带有两个参数的构造方法(显示调用构造方法)   必须放在第一行
    }  
}
class Bird extends Animal{
   public String wing;
   public String name;

   public Bird(String name,int age,String wing){
       super(name,age);
       this.wing = wing;
    }  

   public void fly(){
      System.out.println(super.name+"fly()"+age);//如果name前面不加super会打印什么呢?是nulfly()3还是hehefly()3.答案是前者,因为当子类和父类有同名的字段时,是优先自己的
   } 
}
public class TestDemo{
   public static void main(String[] args){
      Dog dog = new Dog("haha",6);
      System.out.println(dog.name);
      dog.eat();

      Bird bird = new Bird("hehe",3,"我要飞");
      System.out.println(dog.name);
      bird.eat();
      bird.fly();
   }
}

编译并运行该程序,输出如下:

 接下来,我们画一下它的内存布局图:

 (3)super和this的区别

No区别thissuper
1概念访问本类中的属性和方法由子类访问父类的属性和方法
2查找范围先查找本类,如果本类没有就查找父类不查找本类,直接调用父类定义
3特殊表示当前对象

 4.protected关键字

如果把字段设为 private, 子类不能访问 . 但是设成 public, 又违背了我们 " 封装 " 的初衷 . 两全其美的办法就是 protected 关键字 .
No范围privatedefault(包访问权限)protectedpublic
1同一个包中同一类
2同一个包中不同类
3不同包中的子类
4不同包中的非子类

我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者. 因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用public. 另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用

5.更复杂的继承关系

如:

我们写的类是现实事物的抽象. 而我们真正在公司中所遇到的项目往往业务比较复杂, 可能会涉及到一系列复杂的概念, 都需要我们使用代码来表示, 所以我们真实项目中所写的类也会有很多. 类之间的关系也会更加复杂. 但是即使如此, 我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了.

6.final关键字

(1)修饰一个变量或者字段的时候, 表示 常量 (不能修改).

(2)修饰类, 此时表示被修饰的类就不能被继承(final 关键字的功能是 限制 类被继承)

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承

(3)修饰方法

三、组合

和继承类似 , 组合也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果,如:表示一个学校
public class Student { 
    ... 
} 
public class Teacher { 
    ... 
} 
public class School { 
   public Student[] students; 
   public Teacher[] teachers; 
}
组合表示 has - a 语义
在上面的例子中 , 我们可以理解成一个学校中 " 包含 " 若干学生和教师 .
继承表示 is - a 语义
在上上面的 " 动物和狗 " 的例子中 , 我们可以理解成一只狗也 " " 一种动物

四、多态

1.向上转型

(1)向上转型其实就是父类引用 引用子类对象

注意:通过父类引用只能访问父类自己的成员 

 (2)向上转型发生的时机:

直接赋值                                            方法传参                                         方法返回

class Animal{
   public String name;
   public int age;
   private int count;

   public Animal(String name,int age){
      this.name = name;
      this.age = age;
   }

   public void eat(){
      System.out.println("eat()");
   }
}
class Dog extends Animal{
    public Dog(String name,int age){
      super(name,age);
    }  
}
class Bird extends Animal{
   public String wing;
   public String name;

   public Bird(String name,int age,String wing){
       super(name,age);
       this.wing = wing;
    }  

   public void fly(){
      System.out.println(super.name+"fly()"+age);
   } 
}
public class TestDemo{
   public static Animal func(Animal ani){
       ......
   }
   //方法返回
   public static Animal func(Animal ani){
       Dog dog = new Dog("haha",6);
       return dog;
   }
   public static void main(String[] args){
      /*Dog dog = new Dog("haha",6);
      Animal animal = dog;*/
      Animal animal = new Dog("haha",6);//上面两句代码可以浓缩成这一句代码,父类引用引用子类对象  直接赋值
      //方法传参
      Dog dog = new Dog("haha",6);
      func(dog);
   }
}

2.动态绑定与方法重写

(1)什么是动态绑定

父类引用 引用 子类的对象,通过父类引用 调用父类和子类同名的覆盖方法

Java , 调用某个类的方法 , 究竟执行了哪段代码 ( 是父类方法的代码还是子类方法的代码 ) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的 ( 而不是编译期 ), 因此称为 动态绑定

(2)什么是方法重写

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法

子类实现父类的同名方法 , 并且参数的类型和个数完全相同, 这种情况称为 覆写 / 重写 / 覆盖 (Override)
class Animal{
   public String name;
   public int age;
   private int count;

   public Animal(String name,int age){
      this.name = name;
      this.age = age;
   }

   public void eat(){
      System.out.println(name+" eat()");
   }
}
class Dog extends Animal{
    public Dog(String name,int age){
      super(name,age);
    }
    @Override  注解
    public void eat(){
      System.out.println(name+" 狼吞虎咽地eat()");
   }  
}
class Bird extends Animal{
   public String wing;
   public String name;

   public Bird(String name,int age,String wing){
       super(name,age);
       this.wing = wing;
    }  

   public void fly(){
      System.out.println(super.name+"fly()"+age);
   } 
}
public class TestDemo{
   public static void main(String[] args){
      Animal animal = new Dog("haha",6);
      animal.eat();
   }
}

编译并运行该代码,输出如下:

haha 狼吞虎咽地eat()

 为什么不是haha eat()?因为此时发生了动态绑定。编译的时候还不能确定我们此时到底调用谁的方法,运行的时候才知道,所以动态绑定也叫运行时绑定

注意:

①重写时方法不可以是静态方法

②子类的访问修饰限定符要>=父类的访问修饰限定符

③private方法不能重写;被final修饰的方法不能重写

④子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法;子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法

⑤构造方法不能被重写

⑥当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。要想调用父类中被重写的方法,则必须使用关键字 super

⑦重写的返回值不一定相同(特殊情况:协变类型(返回值构成父子类关系))但是这种题出现的比较少,我们要看题中有没有比它更对的或更错的

class Animal{
   public String name;
   public int age;
   private int count;

   public Animal(String name,int age){
      this.name = name;
      this.age = age;
   }

   public Animal eat(){
      System.out.println(name+" eat()");
   }
}
class Dog extends Animal{
    public Dog(String name,int age){
      super(name,age);
    }
    @Override  注解
    public Dog eat(){
      System.out.println(name+" 狼吞虎咽地eat()");
   }  
}
class Bird extends Animal{
   public String wing;
   public String name;

   public Bird(String name,int age,String wing){
       super(name,age);
       this.wing = wing;
    }  

   public void fly(){
      System.out.println(super.name+"fly()"+age);
   } 
}
public class TestDemo{
   public static void main(String[] args){
      Animal animal = new Dog("haha",6);
      animal.eat();
   }
}

这样子返回值构成协变类型也是重写

(3)重写和重载的区别

No区别重载重写
1概念方法名称相同,参数类型及个数不同方法名称及参数类型、个数完全相同
2范围一个类继承关系
3限制没有权限要求被重写的方法不能有比父类风严格的访问控制权限

利用重载也可以实现多态(静态绑定),这是一种静态多态,根据你给的参数的类型和个数推导出你调用哪个函数(编译时多态)

方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现

(4)在构造方法中调用重写的方法() 

class Animal{
   public String name;
   public int age;
   private int count;

   public Animal(String name,int age){
      eat();
      this.name = name;
      this.age = age;
   }

   public Animal eat(){
      System.out.println(name+" eat()");
   }
}
class Dog extends Animal{
    public Dog(String name,int age){
      super(name,age);
    }
    @Override  注解
    public Dog eat(){
      System.out.println(name+" 狼吞虎咽地eat()");
   }  
}
class Bird extends Animal{
   public String wing;
   public String name;

   public Bird(String name,int age,String wing){
       super(name,age);
       this.wing = wing;
    }  

   public void fly(){
      System.out.println(super.name+"fly()"+age);
   } 
}
public class TestDemo{
   public static void main(String[] args){
      //Animal animal = new Animal("haha",6);//①
      Dog dog  = new Dog("haha",6);//②
   }
}

运行①代码,haha eat()

运行②代码,haha 狼吞虎咽地eat(),因为:

构造 Dog 对象的同时 , 会调用父类 的构造方法 . 父类的构造方法中调用了  eat 方法 , 此时会触发动态绑定 , 会调用到 Dog 中的eat()
结论 : " 用尽量简单的方式使对象进入可工作状态 ", 尽量不要在构造器中调用方法 ( 如果这个方法被子类重写 , 就会触发动态绑定, 但是此时子类对象还没构造完成 ), 可能会出现一些隐藏的但是又极难发现的问题

3.向下转型 

向上转型是子类对象转成父类对象 , 向下转型就是父类对象转成子类对象
class Animal{
   public String name;
   public int age;
   private int count;

   public Animal(String name,int age){
      this.name = name;
      this.age = age;
   }

   public void eat(){
      System.out.println(name+" eat()");
   }
}
class Dog extends Animal{
    public Dog(String name,int age){
      super(name,age);
    }
}
class Bird extends Animal{
   public String wing;
   public String name;

   public Bird(String name,int age,String wing){
       super(name,age);
       this.wing = wing;
    }  

   public void fly(){
      System.out.println(super.name+"fly()"+age);
   } 
}
public class TestDemo{
   public static void main(String[] args){
      /*Animal animal = new Bird("hehe",3,"我要飞");
      //animal.fly()//error  通过父类引用只能访问父类自己的成员
      Bird bird = (Bird)animal;
      bird.fly();*/

      /*Animal animal = new Dog("haha",6);
      Bird bird = (Bird)animal;
      bird.fly();*/
      //animal 本质上引用的是一个 Dog 对象, 是不能转成 Bird 对象的. 运行时就会抛出异常
      //所以, 为了让向下转型更安全, 我们可以先判定一下看看 animal 本质上是不是一个 Bird 实例, 再来转换,instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了
      Animal animal = new Dog("haha",6);
      if (animal instanceof Bird) { 
          Bird bird = (Bird)animal; 
          bird.fly(); 
      }
   }
}

4.理解多态

(1)举个例子   打印多种形状

class Shape{
   public void draw(){
      System.out.println("Shape::draw()");
   }
}
class Rect extends Shape{
   @Override
   public void draw(){
      System.out.println("♦");
   }
}
class Flower extends Shape{
   @Override
   public void draw(){
      System.out.println("❀");
   }
}
//--------------------------分割线--------------------------
public class Test{
   public static void drawMap(Shape shape){
      shape.draw();//动态绑定
   } 
   public static void main(String[] args){
      Shape shape1 = new Rect();//向上转型
      Shape shape2 = new Flower();//向上转型
      drawMap(shape1);
      drawMap(shape2);
   }
}

编译应运行该程序,输出如下:

 在这个代码中, 分割线上方的代码是 类的实现者 编写的, 分割线下方的代码是 类的调用者 编写的. 当类的调用者在编写 drawMap 这个方法的时候, 参数类型为 Shape (父类), 此时在该方法内部并不知道, 也不关注当前的 shape 引用指向的是哪个类型(哪个子类)的实例. 此时 shape 这个引用调用 draw 方法可能会有多种不同的表现(和 shape 对应的实例相关), 这种行为就称为 多态

(2)使用多态的好处

①类调用者对类的使用成本进一步降低.
  • 封装是让类的调用者不需要知道类的实现细节.
  • 多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
因此 , 多态可以理解成是封装的更进一步 , 让类调用者对类的使用成本进一步降低
②能够降低代码的 "圈复杂度", 避免使用大量的 if - else
如:假设没有多态,我们要打印多种形状
class Shape{
   public void draw(){
      System.out.println("Shape::draw()");
   }
}
class Rect extends Shape{
   @Override
   public void draw(){
      System.out.println("♦");
   }
}
class Flower extends Shape{
   @Override
   public void draw(){
      System.out.println("❀");
   }
}
class Cycle extends Shape{
   @Override
   public void draw(){
      System.out.println("○");
   }
}
public class TestDemo {
    public static void main(String[] args) {
       Rect rect = new Rect(); 
       Cycle cycle = new Cycle(); 
       Flower flower = new Flower(); 
       String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"}; 
 
       for (String shape : shapes) { 
           if (shape.equals("cycle")) { 
              cycle.draw(); 
           } else if (shape.equals("rect")) { 
              rect.draw(); 
           } else if (shape.equals("flower")) { 
              flower.draw(); 
           } 
       } 
    }
}

现在我们用多态再写一遍代码

class Shape{
   public void draw(){
      System.out.println("Shape::draw()");
   }
}
class Rect extends Shape{
   @Override
   public void draw(){
      System.out.println("♦");
   }
}
class Flower extends Shape{
   @Override
   public void draw(){
      System.out.println("❀");
   }
}
class Cycle extends Shape{
   @Override
   public void draw(){
      System.out.println("○");
   }
}
public class TestDemo {
    public static void main(String[] args) {
       Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),new Rect(), new Flower()}; // 我们创建一个 Shape 对象的数组
       for (Shape shape : shapes) { 
           shape.draw(); 
       } 
    }
}
圈复杂度:一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂. 因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 "圈复杂度". 如果一个方法的圈复杂度太高, 就需要考虑重构。不同公司对于代码的圈复杂度的规范不一样. 一般不会超过10
③可扩展能力更强.
如果要新增一种新的形状 , 使用多态的方式代码改动成本也比较低。 对于类的调用者来说 , 只要创建一个新类的实例就可以了 , 改动成本很低。 而对于不用多态的情况 , 就要把  if - else 进行一定的修改 , 改动成本更高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值