Java作为一种面向对象语言。支持以下基本概念:

  • 多态
  • 继承
  • 封装
  • 抽象
  • 对象
  • 实例
  • 方法
  • 重载

对象和类的概念

  • 类:类是一个模板,它描述一类对象的行为和状态,是抽象的
  • 对象:对象是类的一个实例(对象不是找个女朋友),是具体的, 有状态和行为。例如一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等

下图中男孩(boy)女孩(girl)类(class),而具体的每个人为该类的对象(object)
Java 对象和类 - 图1

image.png

Java中的对象

现在让我们深入了解什么是对象。看看周围真实的世界,会发现身边有很多对象,车,狗,人等等。所有这些对象都有自己的状态和行为。
拿一条狗来举例,它的状态有:名字、品种、颜色,行为有:叫、摇尾巴和跑。
对比现实对象和软件对象,它们之间十分相似。
软件对象也有状态和行为。软件对象的状态就是属性,行为通过方法体现。
在软件开发中,方法操作对象内部状态的改变,对象的相互调用也是通过方法来完成

Java 中的类的变量与方法

类可以看成是创建 Java 对象的模板
通过下面一个简单的类来理解下 Java 中类的定义:

  1. public class Dog {
  2. String breed;
  3. int age;
  4. String color; // 成员变量
  5. static String name; // 静态变量
  6. void Dog(){}
  7. void barking() {
  8. String name = "Ken"; // 局部变量
  9. }
  10. void hungry() {
  11. }
  12. void sleeping() {
  13. }
  14. static void sing() {
  15. }
  16. }
  • 一个类可以包含以下类型变量:
  • 局部变量
    • 在方法、构造方法或者语句块中定义的变量被称为局部变量
    • 只有方法当中才可以使用,出了方法就不能再用
    • 没有默认值,如果要想使用,必须手动进行赋值
    • 变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁(随着方法进栈调用而诞生,随着方法出栈调用完毕而消失) 位于栈内存
  • 成员变量
    • 定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化
    • 整个类全都可以通用
    • 如果没有赋值,会有默认值,规则和数组一样
    • 可以被类中方法、构造方法和特定类的语句块访问
    • 随着对象创建而诞生,随着对象被垃圾回收而消失 位于堆内存
  • 类变量:类变量也声明在类中,方法体之外,但必须声明为 static 类型
  • 一个类可以拥有多个方法
  • 构造方法:
    • Dog()
  • 成员方法:
    • barking()、hungry() 和 sleeping() 都是 Dog 类的方法
  • 类方法(静态方法)
    • sing()

构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认构造方法。
在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法

  1. public class Puppy{
  2. public Puppy(){ }
  3. public Puppy(String name){
  4. // 这个构造器仅有一个参数:name
  5. }
  6. }

创建对象

对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。
  • 实例化:使用关键字 new 来创建一个对象。
  • 初始化:使用 new 创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

  1. public class Puppy{
  2. public Puppy(String name){
  3. //这个构造器仅有一个参数:name
  4. System.out.println("小狗的名字是 : " + name );
  5. }
  6. public static void main(String[] args){
  7. // 下面的语句将创建一个Puppy对象
  8. Puppy myPuppy = new Puppy( "tommy" );
  9. }
  10. }
  11. /*
  12. 小狗的名字是:tommy
  13. */

方法中的可变参数

在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,适用于参数类型定义了但是参数长度未知
… 用在参数上,称之为可变参数
可变参数一定要写在参数列表的末尾位置

  1. 修饰符 返回值类型 方法名(参数类型 ...形参名字){ }
  2. 等价于
  3. 修饰符 返回值类型 方法名(参数类型[] 形参){}
  4. //可变参数的特殊(终极)写法
  5. public static void method(Object...obj){ }

示例:完成数组所有元素的求和

  1. public class ChangeArgs {
  2. public static void main(String[] args) {
  3. int[] arr = { 1, 4, 62, 431, 2 };
  4. int sum1 = getSum(arr);
  5. System.out.println(sum1);
  6. int sum2 = getSum(6, 7, 2, 12, 2121);
  7. System.out.println(sum2);
  8. }
  9. // 可变参数写法
  10. public static int getSum(int... arr) {
  11. int sum = 0;
  12. for (int a : arr) { sum += a; }
  13. return sum;
  14. }
  15. // 原始写法
  16. public static int getSumInitial(int[] arr) {
  17. int sum = 0;
  18. for (int a : arr) { sum += a; }
  19. return sum;
  20. }
  21. }

访问实例变量和方法

通过已创建的对象来访问成员变量和成员方法,如下所示:

  1. // 实例化对象
  2. Object referenceVariable = new Constructor();
  3. // 访问类中的变量
  4. referenceVariable.variableName;
  5. // 访问类中的方法
  6. referenceVariable.methodName();

下面的例子展示如何访问实例变量和调用成员方法:

  1. public class Puppy {
  2. int puppyAge;
  3. public Puppy(String name) {
  4. // 这个构造器仅有一个参数:name
  5. System.out.println("小狗的名字是 : " + name);
  6. }
  7. public void setAge(int age) {
  8. puppyAge = age;
  9. }
  10. public int getAge() {
  11. System.out.println("小狗的年龄为 : " + puppyAge);
  12. return puppyAge;
  13. }
  14. public static void main(String[] args) {
  15. // 创建对象
  16. Puppy myPuppy = new Puppy("tommy");
  17. // 通过方法来设定age
  18. myPuppy.setAge(2);
  19. // 调用另一个方法获取age
  20. myPuppy.getAge();
  21. // 你也可以像下面这样访问成员变量
  22. System.out.println("变量值 : " + myPuppy.puppyAge);
  23. }
  24. }
  25. /*
  26. 小狗的名字是 : tommy
  27. 小狗的年龄为 : 2
  28. 变量值 : 2
  29. */

对象内存图

两个对象使用同一个方法的内存图

两个电饭锅.png

两个引用指向同一个对象的内存图

两个引用指向同一个对象的内存图.png

使用对象类型作为方法的参数

使用对象类型作为方法的参数.png

使用对象类型作为方法的返回值

使用对象类型作为方法的返回值.png

源文件声明规则

源文件的声明规则: 当在一个源文件中定义多个类,并且还有import语句和package语句时,要特别注意这些规则

  • 一个源文件中只能有一个 public 类
  • 一个源文件可以有多个非 public 类
  • 源文件的名称应该和 public 类的类名保持一致。例如:源文件中 public 类的类名是 Employee,那么源文件应该命名为Employee.java
  • 如果一个类定义在某个包中,那么 package 语句应该在源文件的首行
  • 如果源文件包含 import 语句,那么应该放在 package 语句和类定义之间。如果没有 package 语句,那么 import 语句应该在源文件中最前面。
  • import 语句和 package 语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。

类有若干种访问级别,并且类也分不同的类型:抽象类和 final 类等。这些将在访问控制章节介绍。
除了上面提到的几种类型,Java 还有一些特殊的类,如:内部类匿名类

内部类

Java 一个类中可以嵌套另外一个类,语法格式如下

  1. class OuterClass { // 外部类
  2. // ...
  3. class NestedClass { // 嵌套类,或称为内部类
  4. // ...
  5. }
  6. }
  7. #内部类调用
  8. 外部类名.内部类名 对象名字 = new 外部类名().new 内部类名();

要访问内部类,可以通过创建外部类的对象,然后创建内部类的对象来实现。
嵌套类有两种类型:

  • 非静态内部类
  • 静态内部类


权限修饰符套路

  1. 1. 外部类:public / (default)
  2. 2. 成员内部类:public / protected / (default) / private
  3. 3. 局部内部类:什么都不能写


非静态内部类

非静态内部类是一个类中嵌套着另外一个类。 它有访问外部类成员的权限, 通常被称为内部类
由于内部类嵌套在外部类中,因此必须首先实例化外部类,然后创建内部类的对象来实现

  1. class OuterClass {
  2. int x = 10;
  3. class InnerClass {
  4. int y = 5;
  5. }
  6. }
  7. public class MyMainClass {
  8. public static void main(String[] args) {
  9. OuterClass myOuter = new OuterClass();
  10. OuterClass.InnerClass myInner = myOuter.new InnerClass();
  11. System.out.println(myInner.y + myOuter.x);
  12. }
  13. }
  14. /* 15 */

私有的内部类

内部类可以使用 private 或 protected 来修饰,如果你不希望内部类被外部类访问可以使用 private 修饰符:

class OuterClass {
  int x = 10;

  private class InnerClass {
    int y = 5;
  }
}

public class MyMainClass {
  public static void main(String[] args) {
    OuterClass myOuter = new OuterClass();
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
    System.out.println(myInner.y + myOuter.x);
  }
}

以上实例 InnerClass 设置为私有内部类,执行会报错:

MyMainClass.java:12: error: OuterClass.InnerClass has private access in OuterClass
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
             ^

静态内部类

静态内部类可以使用 static 关键字定义,静态内部类我们不需要创建外部类来访问,可以直接访问它:

class OuterClass {
  int x = 10;

  static class InnerClass {
    int y = 5;
  }
}

public class MyMainClass {
  public static void main(String[] args) {
    OuterClass.InnerClass myInner = new OuterClass.InnerClass();
    System.out.println(myInner.y);  //5
  }
}

⚠️注意:静态内部类无法访问外部类的成员

从内部类访问外部类成员

内部类一个高级的用法就是可以访问外部类的属性和方法:

实例
class OuterClass {
  int x = 10;

  class InnerClass {
    public int myInnerMethod() {
      return x;
    }
  }
}

public class MyMainClass {
  public static void main(String[] args) {
    OuterClass myOuter = new OuterClass();
    OuterClass.InnerClass myInner = myOuter.new InnerClass();
    System.out.println(myInner.myInnerMethod()); //10
  }
}

匿名类

Java 中可以实现一个类中包含另外一个类,且不需要提供任何的类名直接实例化。
主要是用于在我们需要的时候创建一个对象来执行特定的任务,可以使代码更加简洁。
匿名类是不能有名字的类,它们不能被引用,只能在创建时用 new 语句来声明它们。
匿名类语法格式:

class outerClass {
    // 定义一个匿名类
    object1 = new Type(parameterList) {
         // 匿名类代码
    };
}

Java 对象和类 - 图7

匿名类继承一个父类

以下实例中,创建了 Polygon 类,该类只有一个方法 display(),AnonymousDemo 类继承了 Polygon 类并重写了 Polygon 类的 display() 方法

class Polygon {
   public void display() {
      System.out.println("在 Polygon 类内部");
   }
}

class AnonymousDemo {
   public void createClass() {

      // 创建的匿名类继承了 Polygon 类
      Polygon p1 = new Polygon() {
         public void display() {
            System.out.println("在匿名类内部。");
         }
      };
      p1.display();
   }
}

class Main {
   public static void main(String[] args) {
       AnonymousDemo an = new AnonymousDemo();
       an.createClass();
   }
}

执行以上代码,匿名类的对象 p1 会被创建,该对象会调用匿名类的 display() 方法,输出结果为:
在匿名类内部

匿名类实现一个接口

interface Polygon {
    public void display();
}

class AnonymousDemo {
    public void createClass() {

        // 匿名类实现一个接口
        Polygon p1 = new Polygon() {
            public void display() {
                System.out.println("在匿名类内部。");
            }
        };
        p1.display();
    }
}

class Main {
    public static void main(String[] args) {
        AnonymousDemo an = new AnonymousDemo();
        an.createClass();  
    }
}

/* 
在匿名类内部。 
*/


Java 包

包主要用来对类和接口进行分类。当开发 Java 程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类

import 语句

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

import java.io.*;


一个简单的例子

在该例子中,我们创建两个类:EmployeeEmployeeTest
首先打开文本编辑器,把下面的代码粘贴进去。注意将文件保存为 Employee.java
Employee 类有四个成员变量:name、age、designation 和 salary。该类显式声明了一个构造方法,该方法只有一个参数。
Employee.java 文件代码:

import java.io.*;

public class Employee{
   String name;
   int age;
   String designation;
   double salary;
   // Employee 类的构造器
   public Employee(String name){
      this.name = name;
   }
   // 设置age的值
   public void empAge(int empAge){
      age =  empAge;
   }
   /* 设置designation的值*/
   public void empDesignation(String empDesig){
      designation = empDesig;
   }
   /* 设置salary的值*/
   public void empSalary(double empSalary){
      salary = empSalary;
   }
   /* 打印信息 */
   public void printEmployee(){
      System.out.println("名字:"+ name );
      System.out.println("年龄:" + age );
      System.out.println("职位:" + designation );
      System.out.println("薪水:" + salary);
   }
}

程序都是从main方法开始执行。为了能运行这个程序,必须包含main方法并且创建一个实例对象
下面给出EmployeeTest类,该类实例化2个 Employee 类的实例,并调用方法设置变量的值
将下面的代码保存在 EmployeeTest.java文件中

EmployeeTest.java 文件代码:

import java.io.*;
public class EmployeeTest{

   public static void main(String[] args){
      /* 使用构造器创建两个对象 */
      Employee empOne = new Employee("RUNOOB1");
      Employee empTwo = new Employee("RUNOOB2");

      // 调用这两个对象的成员方法
      empOne.empAge(26);
      empOne.empDesignation("高级程序员");
      empOne.empSalary(1000);
      empOne.printEmployee();

      empTwo.empAge(21);
      empTwo.empDesignation("菜鸟程序员");
      empTwo.empSalary(500);
      empTwo.printEmployee();
   }
}

编译这两个文件并且运行 EmployeeTest 类,可以看到如下结果:

$ javac EmployeeTest.java
$ java EmployeeTest 

名字:RUNOOB1
年龄:26
职位:高级程序员
薪水:1000.0
名字:RUNOOB2
年龄:21
职位:菜鸟程序员
薪水:500.0