-
包
Java允许使用包将类组织起来。使用包的主要原因是确保类名的唯一性。
(1)类的导入
一个类可以使用所属包中的所有类,以及其他包中的共有类。可以采用两种方式访问另一个包中的公有类。
在每个类之前添加完整的包名:java.time.LocalDate tody = java.time.LocalDate.now();
,这显然很繁琐。
更简单且常用的方式是使用import
语句。import
语句时一种引用包含在包中的类的简明描述。一旦使用了import
语句,在使用类时,就不必写出包的全名了。例:import java.util.*
,也可以导入一个包中的特定类:import java.time.LocalDate
在大多数情况下,只导入所需的包,并不比过多的理睬他们。但是发生命名冲突的时候,就不能不注意包的名字了。例如:java.util
和java.sql
包都有日期(Date
)类。如果在程序中同事导入了两个包:import java.util.*; import java.sql.*;
在程序使用
Date
类的时候,就会出现一个编译错误:Date tody;
,此时编译器无法确定程序使用的是哪一个Date
类。可以增加一个特定的import
语句来解决这个问题:import java.util.*; import java.sql.*; import java.util.Date;
如果这两个
Date
类都需要使用,可以在每个类名前面加上完整的包名:java.util.Date deadline = new java.util.Date(); java.sql.Date tody = new java.sql.Date(...);
在包中定位类时编译器的工作。类文件中的字节码肯定使用完整的包名来引用其他类。
(2)静态导入
import
语句不仅可以导入类,还增加了导入静态方法和静态域的功能
例如,如果在源文件的顶部,添加一条指令:import static java.lang.System.*
就可以使用System类的静态方法和静态域,而不必加类名前缀:out.println("abc");//System.out exit(0);//System.exit(); sqrt(pow(x,2) + pow(y,2));//Math.sqrt(Math.pow(x,2) + Math.pow(y,2));
(3)将类放入包中
想要将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。package com.horstmann.corejava; public class Employee { ... }
如果没有在源文件中放置
package
语句,这个源文件中的类就被放置在一个默认包中。默认包是一个没有名字的包。在此之前,我们定义的所有类都在默认包中。
(4)包作用域
如果没有指定public
或private
这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
(5)注释
类注释必须放在import
语句之后。
每一个方法注释必须放在所描述的方法之前。
@param
变量描述:这个标记将对当前方法的参数部分添加一个条目。 -
类设计技巧
(1)一定要保证数据私有
(2)一定要对数据初始化
(3)不要在类中使用过多的基本类型:用其他类代替多个相关的基本类型的使用。
(4)不是所有的域都需要独立的域访问器和域更改器。 例如:员工的雇用日期是固定的,不可更改
(5)将职责过多的类进行分解
(6)类名和方法名要能够体现它们的职责
(7)优先使用不可变的类。 如果多个线程试图同时更新一个对象,就会发生并发更改,其结果是不可预测的。 -
继承
(1)子类public class manager extends Employee { 添加方法和域 }
关键字
extends
标明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)
、基类(base class)
或者父类(parent class)
;新类称为子类(subclass)
、派生类(derived class)
或者孩子类(child class)
。
尽管Employee
类时一个超类,但并不是因为它由于子类或者拥有比子类更多的功能。实际上恰恰相反,子类比超类拥有的功能更加丰富。public class Manager extends Employee { private double bonus; ... public void setBonus(double bonus) { this.bonus = bonus; } }
在通过扩展超类和定义子类的时候,仅需要之处子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放在超类中,而将具有特殊用途的方法放在子类中。
(2)覆盖方法
子类覆盖超类中的方法public class Manager extends Employee { public double getSalary() { } }
(3)子类构造器
我们可以通过super
实现对超类构造器的调用。使用super
调用构造器的语句必须是子类构造器的第一语句。
如果子类构造器没有显式的调用超类的构造器,则将自动地调用超类默认的构造器。如果超类没有不带参数的构造器,并且子类构造器中有没有显示地调用超类的其他构造器,则Java
编译器将报错。
关键字this
有两个用途:一是引用隐式参数,二是调用该类其他的构造器。同样,super
关键字也有两个用途:一是调用超类的方法,二是调用超类的构造器。
一个对象变量可以指示多种实际类型的现象被称为多态
,在运行时能够自动选择调用哪个方法的现象称为动态绑定
(4)方法调用
假设要调用x.f(args),隐式参数x声明为类C的一个对象。下面是调用过程的详细描述:
1)编译器查看对象的声明类型和方法名。有可能存在多个名字为f,但参数类型不一样的方法。编译器将会一一列举所有C类中名为f的方法和其超类中访问属性为public
且名为f的方法(超类的私有方法不可访问)。
2)编译器将查看调用方法提供的参数类型。如果在所有名为f的方法中存在一个与提供参数类型完全匹配,就选择这个方法。这个过程称为重载解析
==方法的名字和参数列表称为方法的签名。如果在子类中定义了一个与超类签名相同的方法,那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。不过,返回类型不属于签名的一部分,因此,在覆盖方法时,一定要保证返回类型的兼容性。
3)如果是private
方法、static
方法、final
方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式成为静态绑定
4)当程序运行,并采用动态绑定调用方法时,虚拟机一定调用与当前对象的实际类型最合适的那个类的方法。假设x的实际类型是D,它是C类的子类。如果D定义了方法f(String),那么就直接调用它;否则,将在D类的超类中寻找f(String),以此类推。
覆盖一个方法时,子类方法不能低于超类方法的可见性。
(5)阻止继承:final
类和方法
有时候,可能希望阻止人们利用某个类定义子类。不允许拓展的类称为final
类。如果在定义类的时候使用了final
修饰符就表明这个类时final
类。类中的特定方法也可以声明为final
。如果这样,子类不能覆盖这个方法(final类中的所有方法(不包括域)自动地成为final方法)。public class Employee { public final String getName() { return name; } }
(6)抽象类
抽象类不能实例化。
抽象方法充当着占位的角色,它们的具体实现在子类中。拓展抽象类可以有两种选择。一是在抽象类中定义部分抽象方法或不定义抽象类方法,这样就必须把子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
抽象类的子类对象赋值给抽象类,那么抽象类可以调用抽象方法。
例如:public abstract class A { public abstract void method(); } public class B extends { public void method() { ... } } A a = (A)(new B()); a.method();
(7)受保护访问
任何声明为private
的内容对其他类都是不可见的。这点对于子类也完全适用,即子类也不能访问超类的私有域。proteted
可以允许子类访问
(8)所有类的超类
Object
类是Java
中所有类的始祖,在Java
中每个类都由它拓展而来的。如果没有指明超类,Object
就被人为是这个类的超类。可以使用Object
类型的变量引用任何类型的对象:Object obj = new Employee();
(9)equals
equals
用于检测一个对象是否等于另一个对象。在Object
类中,这个方法将判断两个对象是否具有相同的引用。
Java
语言规范要求equals
方法具有下面的特性。
*自反性:对于任何引用x
,x.equals(x)
应当返回true
*对称性:对于任何引用x
和y
。如果x.equals(y)
返回true
,y.equals(x)
也应该返回true
*传递性:对于任何引用x
、y
和z
,如果x.equals(y)
返回true
,y.equals(z)
返回rue
,x.equals(z)
也应该返回true
。
(10)ArrayList
ArrayList
在添加或删除元素时,具有自动调节数组容量的功能,而不需要为此编写任何代码。
ArrayList
是一个采用类型参数的泛型类。为了指定数组列表宝UN的元素对象类型,需要用一对尖括号将类名括起来加在后面,例如,ArrayList<Employee> staff = new ArrayList<Employee>();
两边都使用类型参数有些繁琐:ArrayList<Employee> = new ArrayList();
数组列表自动扩展容量的便利增加了访问元素语法的复杂度。其原因是ArrayList
类并不是Java
程序设计语言的一部分;它是一个由某些人编写且被放在标准库中的一个实用类。
使用set
和get
方法实现改变和访问数组元素的操作。set
只能替换数组列表中已存在的元素,不能添加元素。
(11)对象包装器与自动装箱
有时,需要将int
这个的基本类型转换为对象。所有的基本类型都有一个与之对应的类。
例如,Integer
类对应基本类型int
。通常,这些类称为包装器(wrapper)
。这些对象包装器类拥有很明显的名字:Integer
、Long
、Float
、Double
、Short
、Byte
、Character
、Void
和Boolean
(前6个类派生于公共的超类Number
)。对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final
,因此不能定义它们的子类。ArrayList<Integer> list = new ArrayList(); list.add(3);//会自动变换成下面的形式 //list.add(Integer.valueOf(3)); 这种变换称为自动装箱 int n = list.get(i);//会翻译成 //int n = list.get(i).intValue();//这种形式称为自动拆箱 //编译器将自动地插入一条对象拆箱的指令,然后进行自增计算,最后再将对象装箱 Integer n = 3; n ++;
大多数情况下,容易有一种假象,基本类型与它们的对象包装器是一样的,只是它们的相等性不同。
==
运算符也可以应用于对象包装器对象,只是检测的是对象是否指向同一个存储区域,因此,下面的比较通常不会成立:Integer a = 1000; Integer b = 1000; if(a == b) { ... }
然而,
Java
实现确有可能让它成立。如果将经常出现的值包装到同一个对象中,这种比较就有可能成立。这种不确定的结果并不是我们所希望的。解决这个问题的办法是在两个包装器对象比较时调用equals
方法。
(12)参数数量可变的方法public class PrintStream { public PrintStream printf(String fmt,Object...args){ return format(fmt, args); } }
(13)反射
反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java
代码的程序。特别是在设计或者运行中添加新类时,能够快速地应用开发工具动态地查询新添加类的能力。
能够分析能力的程序称为反射。反射机制功能极其强大,反射机制可以用来:
*在运行时分析类的能力
*在运行时查看对象,例如,编写一个toString方法供所有类使用
*实现通用的数组操作代码
*利用Method对象,这个对象很像C++中的函数指针。
Java学习(四)
最新推荐文章于 2022-05-15 16:13:08 发布