不可变类
如果需要设计一个不可变类,尤其要注意其引用类型Field,如果引用类型Field的类是可变的,就必须采取必要的措施来保护该Feld所引用的对象不会被修改,这样才能创建真正的不可变类。
例如:
//可变类
class Name {
private String firstName;
private String lastName;
public Name(){
}
public Name(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
//不可变类
public class Person{
private final Name name;
public Person(Name name){
this.name = name;
}
public Name getName(){
return this.name;
}
public static void main(String[] args){
Name n = new Name("悟空","孙");
Person p = new Person(n);
System.out.println(p.getName().getFirstName());
n.setFirstName("wuhong");// 这样n改变,person就会改变,破坏了封装,不符合不可变类的特性
System.out.println(p.getName().getFirstName());
}
}
为了解决上面的问题,把上面例子中的Person修改如下即可
public class Person{
private final Name name;
public Person(Name name){
// 创建临时对象
this.name = new Name(name.getFirstName(),name.getLastName());
}
public Name getName(){
// 返回匿名对象
return new Name(name.getFirstName(),name.getLastName());
}
public static void main(String[] args){
Name n = new Name("悟空","孙");
Person p = new Person(n);
System.out.println(p.getName().getFirstName());
n.setFirstName("wuhong");// 这样n改变,person就不会改变了,符合不可变类的特性
System.out.println(p.getName().getFirstName());
}
}
抽象类
例子:
//抽象类(一得一失,一得:可以包含抽象方法,一失:不能实例化)
public abstract class Shape {
{
System.out.println("执行初始化块");
}
private String color;
public abstract double calPerimeter();//抽象方法,必须放在抽象类里,并且不能有方法体
public abstract String getType();
//...
}
除此之外, 当使用 static修饰一个方法时, 表明这个方法属于该类本身,即通过类就可调用该方法, 但如果该方法被定义成抽象方法, 则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误)。因此 static 和 abstract 不能同时修饰某个方法, 即没有所谓的类抽象方法。
接口(interface)
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行得更彻底,则可以提炼出一种更加特殊的“抽象类” —— 接口( interface), 接口里不能包含普通方法,接口里的所有方法都是抽象方法。
接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学。是一种松耦合的设计。
接口可以说是一种纯粹的“抽象类”,即所有方法都是抽象方法。
(但在Java8中进行了改进,允许在接口中定义默认方法、类方法,必须有方法体。默认方法必须加default,类方法必须加static,什么都不加的是public abstract)
接口是从多个相似类中抽象出来的规范。是一种规范和实现分离的设计,是一种松耦合的设计。
接口的定义
[public] interface 接口名 extends 父接口1, 父接口2...
{
零到多个常量定义...
零到多个抽象方法定义...
零到多个内部类、接口、枚举定义...
零到多个默认方法、类方法定义...
}
说明:
修饰符是public或省略,省略是采用包权限访问控制符,只有在相同包下才可以访问
接口中不能包含构造器和初始化块,可以包含成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法)、内部类(内部接口、枚举)。
例子:
public interface Output {
// 接口里定义的成员变量,系统自动加上 public static final 修饰
int MAX_CACHE_LINE = 50;//相当于 public static final int MAX_CACHE_LINE = 50;
// 抽象方法, 系统自动会加上 public abstract
void out();
void getData(String msg);
// 默认方法, 必须手动加上 default,并且必须有方法体
default void print(String... msgs){
for(String s: msgs){
System.out.println(s);
}
}
default void test(){
System.out.println("默认的test()方法");
}
// 类方法,必须手动加上 static ,并且必须有方法体
static String staticTest(){
return "接口中的类方法";
}
}
接口的使用
[public] 类名 extends 父类 implements 接口1,接口2,...
{
类体部分
}
一个类实现了一个或多个接口之后, 这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法); 否则, 该类将保留从父接口那里继承到的抽象方法, 该类也必须定义成抽象类。
一个类实现某个接口时,该类将会获得接口中定义的Field、抽象方法等,因此可以把实现接口理解为一种特殊的继承相当于实现类继承了一个彻底的抽象类。
抽象类与接口的异同
接口和抽象类很像,它们都具有如下特征。
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
但接口和抽象类之间的差别非常大,这种差别主要体现在二者设计目的上。下面具体分析二者的差别。
接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。
当在一个程序中使用接口时,接口是多个模块间的耦合标准:当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
从某种程度上来看,接口类似于整个系统的“总纲”,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。
抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。
抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。
除此之外,接口和抽象类在用法上也存在如下差别。
- 接口里只能包含抽象方法,不包含已经提供实现的方法;抽象类则完全可以包含普通方法。
- 接口里不能定义静态方法;抽象类里可以定义静态方法。
- 接口里只能定义静态常量 Field,不能定义普通 Field;抽象类里则既可以定义普通Fied,也可以定义静态常量 Field
- 接口里不包含构造器:抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
- 接口里不能包含初始化块:但抽象类则完全可以包含初始化块。
- 一个类最多只能有一个直接父类,包括抽象类:但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。
匿名内部类
匿名内部类适合那种只需要一次使用的类。
使用例子:
interface Product{
public double getPrice();
public String getName();
}
public class Hello {
public void test1(Product p){
System.out.println(p.getName()+":"+p.getPrice());
}
public static void main(String[] args) throws ParseException {
Hello h = new Hello();
//匿名内部类
h.test1(new Product() {
@Override
public double getPrice() {
return 10;
}
@Override
public String getName() {
return "小柿子";
}
});
}
}
枚举
Java5新增了一个enum关键字(它与 class、 interface关键字的地位相同),用以定义枚举类。
正如前面看到的,枚举类是一种特殊的类,它一样可以有自己的Fied、方法,可以实现一个或者多个接口也可以定义自己的构造器。
一个Java源文件中最多只能定义一个 public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。
但枚举类终究不是普通类,它与普通类有如下简单区别。
- 枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了 java.lang.Enum类,而不是继承Object类。其中 java.lang.Enum类实现了 java.lang.Serializable和 java.lang.Comparable两个接口。
- 使用enum定义、非抽象的枚举类默认会使用 final修饰,因此枚举类不能派生子类。枚举类的构造器只能使用 private访问控制符,如果省略了构造器的访问控制符,则默认使用pnvate修饰;如果强制指定访问控制符,则只能指定 pnvate修饰符。
- 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加 public static final修饰,无须程序员显式添加。
所有的枚举类都提供了一个 values方法,该方法可以很方便地遍历所有的枚举值。
例子:
enum Season{
//在第一行列出枚举实例
SPRING,SUMMER,AUTUMN,WINTER;
}
public class Hello {
public void judgeSeason(Season s){
switch(s){
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case AUTUMN:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
}
}
public static void main(String[] args) {
Hello h = new Hello();
h.judgeSeason(Season.AUTUMN);
//打印枚举所有实例
for(Season s:Season.values()){
System.out.println(s);
}
/**
秋天
SPRING
SUMMER
AUTUMN
WINTER
*/
}
}
使用jar文件
JAR文件的全称是 Java Archive file,意思就是Java档案文件。通常JAR文件是一种压缩文件,与我们常见的ZP压缩文件兼容,通常也被称为JAR包。
JAR文件与ZP文件的区别就是在JAR文件中默认包含了一个名为 META-INF/MANIFEST.MF的清单文件,这个清单文件是在生成JAR文件时由系统自动创建的。
当开发了一个应用程序后,这个应用程序包含了很多类,如果需要把这个应用程序提供给别人使用,通常会将这些类文件打包成一个JAR文件,把这个JAR文件提供给别人使用。只要别人在系统的CLASSPATH环境变量中添加这个JAR文件,则Java虚拟机就可以自动在内存中解压这个JAR包,把这个JAR文件当成一个路径,在这个路径中查找所需要的类或包层次对应的路径结构。
- 创建jar文件:打开cmd,cd到目录,
jar cf test.jar test
,表示在当前目录下生成一个test.jar,包括 当前目录/test 文件夹下的所有文件。 - 创建jar文件,并显示压缩过程。
jar cvf test.jar test
- 查看jar包内容:
jar tf test.jar
或jar tf test.jar > a.txt
- 解压缩:
jar xf test.jar
带提示信息的解压缩:jar xvf test.jar
解压到当前目录下 - 更新jar文件:
jar uf test.jar Hello.class