Java基础之包、Object类

Java 包

意义

  • 为了更好地组织类,Java提供了包机制,用于区别类名的命名空间

    • 包语句的语法格式 package pkg1[.pkg2[.pkg3…]];

    • // 例如,Something.java 文件
      package net.java.util;
      public class Something{}			// 路径是 net/java/util/Something.java 
      
  • 包(package)可以定义为一组相互联系的类型

    • 类、接口、枚举和注释

    • 为这些类型提供访问保护和命名空间管理的功能

    • // 部分系统包
      java.lang		// 打包基础的类
      java.io		    // 含输入输出功能的函数
      
  • 开发者可以自定义把一组类和接口等打包,并定义自己的包

    • 在实际开发中是值得提倡的
    • 完成类的实现之后,将相关的类分组,可以让他人更容易地确定哪些类、接口、枚举和注释等是相关的
  • 包创建了新的命名空间,所以不会跟其他包中的任何名字产生命名冲突

    • 使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单

创建

  • 创建包时要取一个合适的名字
    • 若其他的源文件包含了该包提供的类、接口、枚举或者注释类型时,都必须将这个包的声明放在这个源文件的开头(导包)
  • 包声明应该在源文件的第一行,每个源文件只能有一个包声明,文件中的每个类型都应用于它
  • 若源文件没有使用包声明,其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)
/* 文件名: Animal.java */
package animals;								// 包声明
interface Animal {								// 在 animals 包中定义接口 Animal
   public void eat();
   public void travel();
}

// 在同一个包中加入该接口的实现
package animals;
public class MammalInt implements Animal{		// 在 animals 包中定义 MammalInt 类实现接口 Animal
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}
// 编译两个文件,它们在同一个命名为animals的子目录中
运行结果:
Mammal eats
Mammal travels

作用

使用包(package)机制

  • 防止命名冲突
  • 进行访问控制
  • 提供搜索和定位 类、接口、枚举 和 注释 等

实现方式

  • 功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用
  • 采用了树形目录的存储方式;同一个包中的类名字是不同的
    • 不同的包中的类的名字是可以相同的
    • 同时调用两个不同包中相同类名的类时,应该加上包名加以区别
    • 因此包可以避免名字冲突
  • 限定了访问权限,拥有包访问权限的类才能访问某个包中的类

import

  • 为了能够使用某一个包的成员,需要在 Java 程序中明确导入该包

    • 使用import语句可完成此功能
    • import 语句应位于 package 语句之后,所有类的定义之前
    • 可以有 0 或 多个
    import package1[.package2…].(classname|*);  // 语法格式
    
  • 在同一个的类想使用另一个类,该包名可以省略

    • 例如:payroll 包已包含Employee

      • payroll 包中定义 Boss
      • Boss 类引用 Employee 类的时无需导入payroll
      package payroll;
      public class Boss{
         public void payEmployee(Employee e){			// 同一个包下直接使用无需导包
            e.mailCheck();
         }
      }
      
  • 不在一个包中

    • 引用其他包的类需要使用类全名描述

    • import 关键字引入

      • 使用类全限定名
      • 或使用通配符 *引入包中所有类:
      import payroll.*; 			// 通配符引入包中所有类
      				
      import payroll.Employee;	// 或明确引入 Employee 类(类全限定名)
      // 注意:类文件中可以包含任意数量的 import 声明;但import 声明必须在包声明之后,类声明之前
      

目录结构

类放在包中会有两种主要的结果

  • 包名成为类名的一部分

  • 包名必须与相应的字节码所在的目录结构相吻合

    • // 包式管理 java 中文件的一种简单方式
      // 类、接口等类型的源码放在一个文本中,文本名就是源码中文件名,并以.java作为扩展名
      
      // 文件名 :  Car.java
      package vehicle; 
      public class Car { }
      
      ....\vehicle\Car.java		// 把源文件放在一个目录中,目录要对应类所在包的名字
      // 正确的类名和路径如下
      类名 -> vehicle.Car
      路径名 -> vehicle\Car.java (在 windows 系统中)
      
  • 通常公司使用其互联网域名的颠倒形式来作为包名

    • 例如:互联网域名是 runoob.com

      • 所有的包名都以 com.runoob 开头

      • 包名中的每一个部分对应一个子目录

      // com.runoob.test 包中有 Runoob.java 源文件,则文件路径为
      ....\com\runoob\test\Runoob.java
      
  1. // 文件名: Runoob.java
    package com.runoob.test;
    public class Runoob {}
    class Google {}
    
    // 编译后的文件路径
    .\com\runoob\test\Runoob.class;
    .\com\runoob\test\Google.class;
    
    // 通配符导入所有 \com\runoob\test\ 中定义的类、接口等
    import com.runoob.test.*; 
    
    • 编译时,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件

      • 输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀
    • 编译后的 .class 文件应该和 .java 源文件一样

      • 放置的目录应该和包的名字对应

      • 但并不要求.class文件和.java的路径一样

      • 可以分开安排源码和类的目录

        • <path-one>\sources\com\runoob\test\Runoob.java
          <path-two>\classes\com\runoob\test\Google.class
          
          • 将类目录分享给他人不用透露源码
          • 这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到程序中使用的所有类型
  2. class path:类目录的绝对路径

    • 系统变量 CLASSPATH 中编译器和JVM将 包名追加到class path之后构造 .class 文件的路径

      • <path- two>\classes:类目录绝对路径class path
      • 包名:com.runoob.test
      • 编译器和 JVM 会在 <path-two>\classes\com\runoob\test 中找 .class 文件
    • class path 可多个路径,多路径应用分隔符分开

      • 默认情况下,编译器和 JVM 查找当前目录
      • JAR 文件包含 Java 平台相关的类,所以默认放在了 class path

类编译与调试

带包(创建及引用)的l类的编译与调试;使用 dos 命令

  • 命令的参数是 类的全限定名,而不是 文件名
  • 打包编译时,会自动创建包目录,不需手动创建包名文件夹
  • 当前目录有多个 java 文件需要编译或打包编译
    • javac -d .\*.java 指令:当前目录下的所有 java 文件根据程序中是否有包声明进行编译或打包编译
  • 类路径不在当前目录下时
    • 用到 java -cp ...指令指定类路径
    • 如:java -cp F:/javaweb2班/20160531mypack1.java
  • 清楚 java 虚拟机根据包声明包导入执行字节码文件的流程。

CLASSPATH

使用 dos 窗口

显示

显示当前 CLASSPATH

  • Windows 平台(DOS 命令行)

    • C:\> set CLASSPATH
      
  • UNIX 平台(Bourne shell )

    • #echo $CLASSPATH
      
删除

删除当前 CLASSPATH

  • Windows 平台(DOS 命令行)

    • C:\> set CLASSPATH=
      
  • UNIX 平台(Bourne shell)

    • #unset CLASSPATH; export CLASSPATH
      
设置

设置CLASSPATH变量

  • Windows 平台(DOS 命令行)

    • C:\> set CLASSPATH=C:\users\jack\java\classes
      
  • UNIX 平台(Bourne shell)

    • #CLASSPATH=/home/jack/java/classes; export CLASSPATH
      

Object 类

定义

  • Java 中Object类是所有类的父类

    • 所有类都继承了 Object,子类可以使用 Object 的所有方法
  • Object 类位于 java.lang 包,编译时会自动导入

    • 定义类时如果没有明确继承,会自动继承 Object,成为 Object 的子类
    // 显式继承
    public class Runoob extends Object{}
    // 隐式继承
    public class Runoob {}
    // 两种方式都是继承了 Object
    

构造函数

Object():构造一个新对象

类的方法

方法列表
方法描述
protected Object clone()创建并返回一个对象的拷贝
boolean equals(Object obj)比较两个对象是否相等
protected void finalize()GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法
Class getClass()获取对象的运行时对象的类
int hashCode()获取对象的 hash
void notify()唤醒在该对象上等待的某个线程(随机唤醒)
void notifyAll()唤醒在该对象上等待的所有线程
String toString()返回对象的字符串表示形式
void wait()当前线程进入等待状态;直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法
void wait(long timeout)让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout超时时间
void wait(long timeout, int nanos)与 wait(long timeout) 方法类似,多了一个 nanos 参数,参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒
clone()
注意事项
  • 对象克隆:对象的复制(全新复制)

    • 使用已有对象内容创建新的对象
  • 通过Object类的 clone() 方法实现

    • protected Object clone() throws CloneNotSuppertedException;
    • 所有类都继承Object类,即所有类都有 clone() 方法
    • 接收对象必须强转
  • 实现克隆需要对象所在类实现Cloneable接口

    • Object类本身没有实现 Cloneable 接口
      • 不重写 clone()方法直接进行调用会发生CloneNotSupportedException 异常
    • 该接口没有任何方法提供,描述的是一种能力
      • 接口可以定义标准 或 描述一种能力的标识;作为标识接口
  • 使用规则

    • 如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone()而得到的对象

    • 例如

      class A implements Cloneable{   				// 实现 Cloneable 接口表明此类可以克隆
      	public A clone() {
      		return new A();							// 直接调用构造器
      	}
      }
       
      class B extends A{
      	public B clone() {
      		try{
      			return (B) super.clone();			// 得到错误对象,super.clon() 返回 A 的对象
      		}catch (CloneNotSupportedException e){
      			throw new AssertionError();
      		}
      	}
      }
      
      // 正确重写 clone()
      public A clone(){
          return (A)super.clone();					// 调用父类 clone() 方法,强转为本类类型
      }
      
浅克隆
  • 成员变量是值类型,复制一份给克隆对象

  • 成员变量是引用类型,则将对象的地址复制一份给克隆对象

    • 即原型对象和克隆对象的成员变量指向相同的内存地址
  • 浅克隆当对象被复制时只复制它本身和其中包含的值类型的成员变量

    • 引用类型的成员对象并没有复制
  • 可以重写 clone() 方法,直接赋值引用等实现

浅克隆

深克隆
  • 成员变量是值类型或引用类型,都复制一份给克隆对象

    • 深克隆将对象本身和其所包含的所有成员变量都复制
      深克隆
  • 实现方法

    • 重写 clone() 方法

      • 原型对象的引用类型属性成员变量类也需要实现Cloneable接口对clone()方法进行重写

      • 在原型对象的 clone() 方法中同时克隆引用类型成员变量

        public class User implements Cloneable{							// 原型类型
            private String userName;									// 值类型
            private HavingDinner havingDinner;							// 引用类型对象
            // 其他 set、get 方法略
            public void setHavingDinner(HavingDinner havingDinner) {
                this.havingDinner = havingDinner;
            }
        
            @Override
            protected Object clone() throws CloneNotSupportedException{		// 重写 clone 方法
                User user = (User)super.clone();							// 进行浅克隆						
                user.setHavingDinner((HavingDinner)getHavingDinner().clone());	// 对引用类型成员变量克隆,实现深克隆
                return user;
            }
        }
        public class HavingDinner implements Cloneable{						// 引用类型成员变量的类
            private String foodName;
            @Override
            protected Object clone() throws CloneNotSupportedException{		// 该类也实现克隆
                return super.clone();
            }
        }
        
    • 序列化、反序列化

      • 把对象序列化输出到流里面,然后把流里面的数据反序列化出来,得到一个新的对象
equals()
  • Objectequals()方法

    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    • 默认使用 == 判断对象
      • 判断两个对象引用指向的是同一个对象
      • 即比较两个对象的内存地址是否相等
  • 应该重写该方法自定义判断两个对象相等的逻辑

    1. 两个对象地址相等则一定相等

      • 否则进一步判断
    2. 另一个对象为 null 或两个对象的类型不同则一定不相等

    3. 逐个对对象的属性进行判断是否相等

      • 都相等时判断两个对象相等
      @Override
      public boolean equals(Object o) {						// 重写 equals 方法
          if(this == o)										// 两个对象地址相等返回 true
              return true;			
          if(o == null || getClass() != o.getClass())			// 比较对象为 null 或两个对象运行类型不同返回 false
              return false;
          Demo demo = (Demo)o;								// 将比较对象强转为当前对象编译类型
          // 逐项比较两个对象的属性并返回结果
          return id == demo.id && age == demo.age && Objects.equals(name, demo.name) && Objects.equals(date, demo.date);
      }
      
notify()
  • 随机唤醒正在等待的线程
  • 只能被作为此对象监视器的所有者的线程调用
    • 成为对象监视器的所有者,三种方法
      • 执行对象的同步实例方法
      • 使用 synchronized 内置锁
      • 对于 Class 类型的对象,执行同步静态方法
    • 如果当前线程不是此对象监视器的所有者
      • 抛出 IllegalMonitorStateException 异常
  • 所有线程都在当前对象等待时,只会选择一个线程
    • 选择是任意性的,并在对实现做出决定时发生
    • 一次只能有一个线程拥有对象的监视器
  • 线程在对象监视器上等待可以调用 wait() 方法
notifyAll()
  • 唤醒所有正在等待的线程
  • notifyAll()notify()
    • notifyAll():唤醒此对象监视器上等待的所有线程
    • notify():随即唤醒一个线程
  • 当前线程不是对象监视器的所有者
    • 调用方法同样发生IllegalMonitorStateException异常
wait() 、wait(long timeout)
  • 线程等待、超时等待;直到被 notify 方法唤醒

  • 当前线程必须是此对象的监视器所有者

    • 否则发生IllegalMonitorStateException异常
  • 当前线程在等待之前或在等待时被任何线程中断抛出InterruptedException异常

  • wait(long timeout)

    • 传递的参数不合法,抛出IllegalArgumentException异常
    • timeout参数为 0,则不会超时,一直进行等待,类似于 wait() 方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值