【JavaSE】面向对象编程

目录

1.包

1.1导入包中的类

1.2静态导入

1.3将类放到包中

1.3.1基本规则  

1.3.2操作步骤

1.4包的访问权限控制

1.5常见的系统包

2.继承

 super:[不能出现在静态方法当中]表示的是父类对象的引用

2.1protected 关键字

2.2final 关键字

3.组合

4.多态

4.1向上转型

 4.2动态绑定

4.3静态绑定

4.4方法重写

4.5 到底什么是多态?

5.抽象类  

5.1注意事项

6.接口  

6.1语法规则 

6.2总结接口注意事项:

6.3实现多个接口 

6.4三个常见的接口

6.4.1 Comparable接口

6.4.2 Comparator接口


1.包

(package) 是组织类的一种方式 .
使用包的主要目的是保证类的唯一性 . 包名必须是小写字母
  
例如 , 你在代码中写了一个 Test . 然后你的同事也可能写一个 Test . 如果出现两个同名的类 , 就会冲突 , 导致代码不能编译通过.

1.1导入包中的类


我们使用 import 来导入指定包中的类,import  java.包名.* 表示导入该包底下的所有类,但是并不是一下子全部导入,而是你用到哪个类就导入哪个,但是我们更建议显式的指定要导入的类名. 否则还是容易出现冲突的情况。如下

 // 编译出错

因为util和sql底下都有Date类这个时候就需要使用完整的类名

显示使用就没有冲突了


1.2静态导入

使用 import static 可以导入包中的静态的方法和字段 

使用这种方式可以更方便的写一些代码, 例如 

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 );
  }
}

1.3将类放到包中


1.3.1基本规则  

  •  在文件的最上方加上一个 package 语句指定该代码在哪个包中.
  •  包名需要尽量指定成唯一的名字 , 通常会用公司的域名的颠倒形式 ( 例如 com.lzn.demo1 ).
  • 包名要和代码路径相匹配 . 例如创建 com.bit.demo1 的包 , 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
  • 如果一个类没有 package 语句 , 则该类被放到一个默认包中 .

1.3.2操作步骤

1) IDEA 中先新建一个包: 右键 src -> 新建 ->  

2) 在弹出的对话框中输入包名 , 例如 com.lzn.demo4

3) 在包中创建类 , 右键包名 -> 新建 -> , 然后输入类名即可 .

4) 此时可以看到我们的磁盘上的目录结构已经被 IDEA 自动创建出来了

5) 同时我们也看到了 , 在新创建的 Test.java 文件的最上方 , 就出现了一个 package 语句
 


1.4包的访问权限控制

我们已经了解了类中的 public private. private 中的成员只能被类的内部使用 . 如果某个成员不包含public 和 private 关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用

 下面的代码给了一个示例. Test  TestDemo是同一个包中, Test2 是其他包中.

Test.java
package com.lzn.demo1;
public class Test {
    int value = 10;
}
TestDemo.java
package com.lzn.demo1;

public class TestDemo {
    public static void main(String[] args) {
      Test test = new Test();
      System.out.println(test.value);
    }
}

// 执行结果, 能够访问到 value 变量
10

Test2.java

package com.lzn.demo2;

import com.lzn.demo1.Test;
public class Test2 {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.value);
    }
}

//编译出错
java: value在com.lzn.demo1.Test中不是公共的; 无法从外部程序包中对其进行访问

1.5常见的系统包

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

2.继承

面向对象的基本特征:

封装:不必要公开的成员和方法使用 private 关键字修饰,意义:安全性。

继承:对共性的抽取。使用 extends 关键字进行处理,意义:可以对代码进行重复使用。

多态:下面再说。

 

1.Java中的继承属于单继承,不能同时继承两个及以上的类;

2.子类构造的同时要帮助父类进行构造,用构造方法 

3.对于父类的 private 的字段和方法, 子类中是无法访问的

 super:[不能出现在静态方法当中]表示的是父类对象的引用

  1. super();//调用父类的构造方法,只能出现在构造方法中,并且只能放在第一行
  2. super.func();//调用父类的普通方法
  3. super.data;//调用父类的成员属性

2.1protected 关键字

我们发现 , 如果把字段设为 private, 子类不能访问 . 但是设成 public, 又违背了我们 " 封装 " 的初衷 .
两全其美的办法就是 protected 关键字 .
  • 对于类的调用者来说, protected 修饰的字段和方法是不能访问的
  • 对于类的 子类 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的

 小结: Java 中对于字段和方法共有四种访问权限

  • private: 类内部能访问, 类外部不能访问
  • 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
  • protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
  • public : 类内部和类的调用者都能访问

2.2final 关键字

曾经我们学习过 final 关键字, 修饰一个变量或者字段的时候, 表示 常量 (不能修改).

final int a = 10;
a = 20; // 编译出错

final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.

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

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


3.组合

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

例如表示一个学校:

public class Student {
...
}
public class Teacher {
...
}
public class School {// 组合表示has-a的含义,学校里面包含若干老师和学生
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到特殊的语法 ( 诸如 extends 这样的关键字 ), 仅仅是将一个类的实例作为另外一个类的字段 .
这是我们设计类的一种常用方式之一

4.多态

 对于多态我们要一步一步来理解

4.1向上转型

public class Text {

    public static void main(String[] args) {
        Animal animal = new Dog("小小",5);  
    }
}

此时animal是一个父类 (Animal) 的引用, 指向一个子类Dog的实例. 这种写法称为 向上转型.

也就是子类往父类的方向转.

什么情况下会发生向上转型? 

  • 直接赋值:就是像上面父类 (Animal) 引用, 指向一个子类(Dog)的实例
  • 方法传参:

  • 方法返回


 4.2动态绑定

动态绑定的两个前提:

  1. 父类引用 引用 子类的对象(向上转型)
  2. 通过这个父类引用调用父类和子类 同名的覆盖 方法(注:通过父类引用只能访问父类自己的成员)
public class Animal {
   protected String name;
   protected int age;
    Animal(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "正在eat()");
    }
}


public class Dog extends Animal{

    public Dog(String name, int age){
        super(name, age);
    }
    public void eat(){
        System.out.println(name + "狼吞虎咽的eat()");
    }
}


public class Text {

    public static void main(String[] args) {
        Animal animal = new Dog("小小",5);
        animal.eat();
    }
}


//运行结果
小小狼吞虎咽的eat()

 以上代码就是发生了动态绑定,通过父类引用调用了子类的同名覆盖方法,为什么会叫动态绑定呢?我们可以通过反汇编看看


4.3静态绑定

 静态绑定就是编译时绑定,又叫编译时多态,是通过重载来实现的

public class Dog extends Animal{

    public Dog(String name, int age){
        super(name, age);
    }
  
    public void func(int x){
        System.out.println("int");
    }
    public void func(int x,int y){//重载func()函数
        System.out.println("int,int ");
    }
    public void func(int x,int y,int z){//重载func()函数
        System.out.println("int,int,int ");
    }
}

public class Text {

    public static void main(String[] args) {
        Dog dog = new Dog("小小",5);
        dog.func(10);//调用带有一个参数的func方法
        dog.func(10,20)//调用带有两个参数的func方法
    }
}

以上代码就是发生了静态绑定,通过重载实现,为什么会叫动态绑定呢?我们可以通过反汇编看看


4.4方法重写

针对刚才的 eat 方法来说 :
子类实现父类的同名方法 , 并且参数的类型和个数完全相同 , 这种情况称为 覆写 / 重写 / 覆写 (Override) .

运行时多态是通过方法重写实现,上面的代码就是方法重写,在子类重写了eat()方法

@override

重写(覆盖,覆写):(不要和重载弄混淆)

  1. 方法名相同
  2. 参数列表相同(个数和类型相同)
  3. 返回值相同
  4. 父子类的情况下

重写要注意的几个点:

  • static方法不能重写
  • private方法不能重写
  • 被final修饰的方法不能重写
  • 子类的访问修饰限定符要大于或者等于父类的访问修饰限定符 
小结 : 重载和重写的区别 .
NO区别重载(overlode)覆写(override)
1概念方法名称相同,参数的类型及个数不同方法名称,返回值类型,参数的类型及个数完全相同。
2范围一个类继承关系
3限制没有权限要求子类的访问修饰限定符要大于或者等于父类的访问修饰限定符 

4.5 到底什么是多态?

了解了上面的向上转型、动态绑定以后,那么到底什么是多态呢?我们可以看一下下面的代码理解一下

Shape.java

public class Shape {//定义一个Shape类作为父类
    public void draw() {
    }
}

Circle.java

public class Circle extends Shape{
    @Override
    public void draw() {//重写draw方法
        System.out.println(" ⚪ ");
    }
}
Flower.java
public class Flower extends Shape {
    @Override
    public void draw() {//重写draw方法
        System.out.println(" ❀ ");
    }
}

Triangle.java 

public class Triangle extends Shape{
    @Override
    public void draw() {//重写draw方法
        System.out.println(" △ ");
    }
}

 Test.java

public class Test2 {
    public static void show(Shape shape) {
        shape.draw();
    }
    public static void main(String[] args) {
       Flower flower = new Flower();
       Circle circle = new Circle();
       Triangle triangle = new Triangle();
       show(flower);
       show(circle);
       show(triangle);
    }
}

//运行结果
 ❀ 
 ⚪ 
 △ 

看完以上的运行结果我们会发现,通过同一个引用(Shape)调用同一个方法(draw)会表现出不同的结果,这就是多态。

无论是哪种编程语言, 多态的核心都是让调用者不必关注对象的具体类型. 这是降低用户使用成本的一种重要方式.


5.抽象类  

在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)

abstract public class Shape {//抽象类
    abstract public void draw();//抽象方法
}
  • draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
  • 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类

5.1注意事项

  1. 抽象类是不可以被实例化
  2. 因为不能被实例化,所以这个抽象类实际上只能被继承
  3. 抽象类当中可以包含和普通类一样的成员和方法
  4. 一个普通类,继承了一个抽象类,那么这个普通类需要重写这个抽象类中的所有抽象方法
  5. 一个抽象类A。继承了一个抽象类B,那么这个抽象类A可以不实现抽象类B的抽象方法
  6. 结合第五点,当抽象类A再次被普通类继承,那么抽象类A和抽象类B的抽象方法,都要被重写
  7. 抽象类不能被final修饰,抽象方法也不能被final修饰。(因为final修饰的类不能被继承,final修饰的方法不能被重写,而抽象类就是为了被继承,抽象方法就是为了被重写)
  8. 抽象类最大的作用就是被继承

6.接口  

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

6.1语法规则 

在刚才的打印图形示例中, 我们的父类Shape并没有包含别的非抽象方法, 也可以设计成一个接口 

IShape.java


public interface IShape {//定义一个IShape接口
    void draw();
}


Circle.java

public class Circle implements IShape {//实现IShape接口
    @Override
    public void draw() {
        System.out.println(" ⚪ ");
    }
}

Test2.java

public class Test2 {

    public static void main(String[] args) {
        Shape shape = new Circle();
        shape.draw();
    }
}


//运行结果
 ⚪
  • 使用 interface 定义一个接口
  • 接口中的方法一定是抽象方法, 因此可以省略 abstract
  • 接口中的方法一定是 public, 因此可以省略 public
  • Cycle 使用 implements 继承接口. 此时表达的含义不再是 "扩展", 而是 "实现"
  • 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
  • 接口不能单独被实例化

    扩展 (extends) vs 实现 (implements)
    扩展指的是当前已经有一定的功能了 , 进一步扩充功能 .
    实现指的是当前啥都没有 , 需要从头构造出来 .

接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).

public interface IShape {
    void draw();
    public static final int c = 10;
}
其中的 public, static, fifinal 的关键字都可以省略 . 省略后的 num 仍然表示 public 的静态常量
提示 :
1. 我们创建接口的时候 , 接口的命名一般以 大写字母 I 开头.
2. 接口的命名一般使用 " 形容词 " 词性的单词 .
3. 阿里编码规范中约定 , 接口中的方法和属性 不要加任何修饰符号 , 保持代码的简洁性

6.2总结接口注意事项:

  1. 使用interface来修饰,interfa  IA{}
  2.  接口中的普通方法不能有具体的实现,非要有具体的实现,要用关键字default修饰这个方法
  3. 接口当中可以有static方法
  4. 接口里面的所有方法都是public的
  5. 抽象方法,默认是public abstract的
  6. 接口不可以被实例化
  7. 接口当中的成员变量默认是public static final修饰的所以必须初始化
  8. 当一个类实现一个接口时,重写这个接口中的方法的时候,必须要在重写后的方法前面加上public,因为不写的话是包访问权限,接口中方法是public权限,如果不写那么重写后的方法访问权限比原来方法的访问权限小就会报错。
  9. 一个类可以通过关键字extends继承一个抽象类或者普通类,但是只能继承一个类,同时这个类可以通过implements实现多个接口,接口之间用逗号隔开就好
  10. 接口和接口之间可以通过extends来操作,意为:拓展,一个接口A通过extends来拓展另外一个接口B的功能,此时当一个类D通过implements实现这个接口A的时候,此时重写的方法不仅仅是A的方法还有接口A从接口B拓展来的方法也要被重写。

6.3实现多个接口 

Animal.java

public class Animal {
    protected String name;//定义一个name字段

    public Animal(String name) {//构造方法
        this.name = name;
    }
}

IJumping.java

public interface IJumping {//IJumping接口
    void jump();
}


IRunning.java

public interface IRunning {//IRunning接口
    void  run();
}


ISwimming.java

public interface ISwimming {//ISwimming接口
    void swim();
}

//下面定义几个动物
Cat.java
小猫是动物,并且具有跳和跑的特性

public class Cat extends Animal implements IJumping,IRunning{

    public Cat(String name) {
        super(name);
    }

    @Override
    public void jump() {
        System.out.println(this.name + "猫咪正在欢快的跳");
    }

    @Override
    public void run() {
        System.out.println(this.name + "猫咪正在焦急的跑");
    }
}


Fish.java

//小鱼是动物并且会游泳

public class Fish extends Animal implements ISwimming {

    public Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(this.name + "小鱼正在欢快的游泳");
    }
}

Frog.java
//青蛙是动物会跳会游泳
public class Frog extends Animal implements IJumping,ISwimming{
    public Frog(String name) {
        super(name);
    }

    @Override
    public void jump() {
        System.out.println(this.name + "青蛙正在一蹦一跳");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "青蛙正在游泳");
    }
}
上面的代码展示了 Java 面向对象编程中最常见的用法 : 一个类继承一个父类 , 同时实现多种接口 .
继承表达的含义是 is - a 语义 , 而接口表达的含义是 具有 xxx 特性 .
猫是一种动物 , 具有会跑会跳的特性 .
青蛙也是一种动物 , 既能跑 , 也能游泳
🐟也是一种动物 , 能游泳
这样设计有什么好处呢 ? 时刻牢记多态的好处 , 让程序猿 忘记类型 . 有了接口之后 , 类的使用者就不必关注具体类型 , 而只关注某个类是否具备某种能力.
例如 , 现在实现一个方法 , "walk "
public static void walk(IJumping iJumping) {
        iJumping.jump();
    }
在这个 walk 方法内部 , 我们并不关注到底是哪种动物 , 只要参数是会跳的 , 就行
  public static void main(String[] args) {
        Cat cat =  new Cat("大大");
        Frog frog = new Frog("小小");
        walk(cat);
        walk(frog);
    }
//运行结果
大大猫咪正在欢快的跳
小小青蛙正在一蹦一跳

甚至参数可以不是 "动物", 只要会跳!  

public class Robot implements IJumping{//机器人class实现IJjumping接口
    @Override
    public void jump() {//重写jump方法
        System.out.println("机器人正在跳");
    }
}
/
public static void main(String[] args) {
       
        walk(new Robot());
    }

//运行结果
机器人正在跳

6.4三个常见的接口

6.4.1 Comparable接口

Student.java

public class Student implements Comparable<Student>{//实现Comparable 接口
    String name;
    int age;
    double score;

    public Student(String name, int age, double score) {//构造方法
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override//重写Comparable接口中的CompareTo方法
    public int compareTo(Student o) {//谁调用comparaTo谁就是this
        return this.age - o.age;//按照年龄进行比较
    }
}

Test.java 

public class Test {
  
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("Amy",18,99.7);
        students[1] = new Student("Max",17,86.7);
        students[2] = new Student("Cho",20,56.7);
//sort中调用CompareTo的时候会动态绑定我们重写的方法,就会按照age排序了
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }

}

//运行结果
[Student{name='Max', age=17, score=86.7}, Student{name='Amy', age=18, score=99.7}, Student{name='Cho', age=20, score=56.7}]

 但是Comparable接口有一个比较大的缺点,就是对类的入侵性特别强,一旦写好可不敢轻易动,如果我们要按照score比较,那么我们又需要对类中的compareTo方法进行修改,不太好,所以下面我们介绍一个Comparator接口

6.4.2 Comparator接口

Student.java 

public class Student {
    String name;
    int age;
    double score;

    public Student(String name, int age, double score) {//构造方法
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

}

class  AgeComparator implements Comparator<Student> {//通过年龄进行比较的比较器
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

class  ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.score - o2.score);//由于年龄是double类型所以要强转成int
    }
}

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

 Test.java

public class Test {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("Amy",18,99.7);
        students[1] = new Student("Max",17,86.7);
        students[2] = new Student("Cho",20,56.7);
        AgeComparator ageComparator = new AgeComparator();
        NameComparator nameComparator = new NameComparator();
        ScoreComparator scoreComparator = new ScoreComparator();
//这个时候我们需要按照什么进行比较,就只需要多传一个比较器就好
        Arrays.sort(students,ageComparator);
        System.out.println(Arrays.toString(students));
    }

}

//运行结果
[Student{name='Max', age=17, score=86.7}, Student{name='Amy', age=18, score=99.7}, Student{name='Cho', age=20, score=56.7}]

 Comparator接口就十分灵活了,对类的入侵性也非常的弱,用哪个接口取决于你的业务,一般推荐比较器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值