1 处理对象
1.1 打印对象和toString()方法
toString()方法是Object类里的一个实例方法,所有的Java类都是Object类的子类,因此所有的Java对象都具有toString()方法。下面两种表达效果一样。
System.out.println(p);
System.out.println(p.toString());
toString()方法是一个非常特殊的方法,它是一个“自我描述”方法,其主要功能是:当直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。
该方法的返回值为:类名+@+hashCode,因此用户需要重写此方法来实现自我描述。
1.2 ==和equals方法
==判断两个变量是否相等时,如果两个变量是基本类型变量,且都是数值类型,则只要两个变量的值相等,就返回true。
对于两个引用类型变量,只有它们指向同一个对象时,==判断才会返回true。
==不能用于比较类型上没有父子关系的两个对象。
2 类成员
static修饰的成员就是类成员,类成员有类变量、类方法、静态初始化块三个成分,static不能修饰构造器。static修饰的成员属于整个类,不属于单个实例。
Java类里只能包含成员变量、方法、构造器、初始化块、内部类(包括接口、枚举)5种成员。
2.1 单例类
如果一个类始终只能创建一个实例,则这个类被称为单例类。
在一些特殊场景下,要求不允许自由创建该类的对象,而只允许为该类创建一个对象。为避免其他类自由创建该类的实例,应该把该类的构造器使用private修饰,从而把该类的所有构造器隐藏起来。
根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰。除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,因此该成员变量需要被上面的静态方法访问,故该成员变量必须使用static修饰。
class Singleton{
//使用一个类变量来缓存曾经创建的实例
private static Singleton instance;
//对构造器使用private修饰,隐藏该构造器
private Singleton() {}
//提供一个静态方法,用于返回Singleton实例,该方法可以加入自定义控制,保证只产生一个Singleton对象
public static Singleton getInstance() {
if (instance==null) {
instance=new Singleton();
}
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) {
//创建Singleton对象不能通过构造器,只能通过getInstance方法来得到实例
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);//将输出true
}
}
3 final修饰符
final关键字可用于修饰类、变量、方法,表示它修饰的类、变量、方法不可改变。
3.1 final成员变量
Java语法规定:final修饰的成员变量必须由程序员显式地指定初始值。
归纳起来,final修饰的类变量、实例变量能指定初始值的地方如下:
类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定;
实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
3.2 final局部变量
局部变量也必须显式初始化。既可以在定义时指定默认值,也可以不指定,在后面代码中赋值,但只能赋一次。
3.3 final方法和类
final修饰的方法不可被重写,如果不希望子类重写父类的某个方法,则可以使用final修饰该方法。
final修饰的类不可以有子类。
4 抽象类
4.1 抽象方法和抽象类
使用abstract来修饰;有抽象方法的类只能被定义为抽象类,抽象类可以没有抽象方法;抽象方法不能有方法体;抽象类不能被实例化;子类必须实现父类的所有抽象方法。
public abstract String test();
5 接口
5.1 接口的概念和定义
接口是从多个相似类中提取出来的规范,不提供任何实现,接口体现的是规范和实现分离的设计哲学。接口里通常是定义一组公用方法。
1.定义接口的修饰符可以是public或省略,省略的话默认采用包权限访问;
2.一个接口可以有多个直接父接口,接口只能继承接口,不能继承类;
3.接口里可以包含成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法或私有方法)、内部类(内部接口、枚举);这些成员都是public访问权限,可以省略;
4.接口中的静态常量默认修饰符为public static final;普通方法默认public abstract,且普通方法不能有方法体;类方法、默认方法、私有方法都必须有方法体。
public interface Demo {
int cons=5; //成员变量只能是常量
void out();
default void print() { //默认方法需要使用default修饰
System.out.println("默认的test()方法");
}
static String staticTest() {
return "接口里的类方法";
}
private void foo() {
System.out.println("foo私有方法");
}
private static void bar() {
System.out.println("bar私有静态方法");
}
}
5.2 面向接口编程
1. 简单工厂模式
有一个场景:假设程序中有个Computer类需要组合一个设备,现在有两种选择:直接让Computer类组合一个Printer,或者让Computer类组合一个Output,考虑采用哪种设计模式?如果Printer对象需要更改,则如何设计更方便?
工厂模式建议Computer类组合一个Output类型的对象,将Computer类和Printer类完全分离。通过与Output接口耦合,使用Output工厂来负责生成Output对象。
//定义一个Output接口
public interface Output {
int MAX_CACHE_LINE=50;
void out();
void getData(String msg);
}
//定义Computer类
public class Computer {
private Output out;
public Computer(Output out) {
this.out=out;
}
public void keyIn(String msg) {
out.getData(msg);
}
public void print() {
out.out();
}
}
//定义Output工厂
public class OutputFactory {
//返回Output实现类的实例,具体创建哪一个实现类的对象由该方法决定
public Output getOutput() {
return new Printer();
}
public static void main(String[] args) {
OutputFactory of = new OutputFactory();
Computer c=new Computer(of.getOutput());
c.keyIn("java ee");
c.keyIn("java");
c.print();
}
}
//定义Printer类实现Output接口
public class Printer implements Output{
private String[] printData=new String[MAX_CACHE_LINE];
private int dataNum=0;
public void out() {
while(dataNum>0) {
System.out.println("打印机打印:"+printData[0]);
System.arraycopy(printData, 1, printData, 0, --dataNum);
}
}
public void getData(String msg) {
if(dataNum>=MAX_CACHE_LINE) {
System.out.println("输出队列已满,添加失败");
}else {
printData[dataNum++]=msg;
}
}
}
2. 命令模式
有一个场景:某个方法需要完成一个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。
具体示例:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组元素时如何处理这些元素,需要在调用该方法时指定具体的处理行为。
//定义一个接口
public interface Command {
//接口里定义的process方法用于封装“处理行为”
void process(int[] target);
}
//数组的处理类,因无法确认处理数组的具体方法,所以利用Command参数,此参数负责处理数组的具体行为
public class ProcessArray {
public void process(int[] target,Command cmd) {
cmd.process(target);
}
}
//定义数组的处理方式一
public class PrintCommand implements Command{
public void process(int[] target) {
for(int tmp:target) {
System.out.println("输出目标数组的元素:"+tmp);
}
}
}
//定义数组的处理方式二
public class AddCommand implements Command{
public void process(int[] target) {
int sum=0;
for(int tmp:target) {
sum+=tmp;
}
System.out.println("数组的和为:"+sum);
}
}
//测试
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa=new ProcessArray();
int[] target= {1,2,3,4};
pa.process(target, new PrintCommand());
System.out.println("..........");
pa.process(target, new AddCommand());
}
}
运行结果:
输出目标数组的元素:1
输出目标数组的元素:2
输出目标数组的元素:3
输出目标数组的元素:4
..........
数组的和为:10
6 内部类
7 枚举类
例如四季类,只有四个对象。这种实例有限而且固定的类称为枚举类。
用enum关键字定义枚举类;枚举类可以实现一个或多个接口,但不能显示继承父类;使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类;枚举类的构造器只能使用private修饰;枚举类的所有实例必须在枚举类的第一行显式列出,系统会自动添加public static final修饰。
7.1 枚举类入门
//定义一个Season枚举类
public enum Season {
spring,summer,fall,winter;
}
//测试类
public class SeasonTest {
public void judge(Season s) {
switch(s) {//switch语句的表达式可以是枚举类
case spring:
System.out.println("春");
break;
case summer:
System.out.println("夏");
break;
case fall:
System.out.println("秋");
break;
case winter:
System.out.println("冬");
break;
}
}
public static void main(String[] args) {
for(Season s:Season.values()) {//枚举类提供的values()方法可以遍历所有的枚举值
System.out.println(s);
}
new SeasonTest().judge(Season.spring);//使用EnumClass.variable调用某个实例
}
}
运行结果:
spring
summer
fall
winter
春
7.2 枚举类的成员变量、方法和构造器
public enum Gender {
MALE("男"),FEMALE("女");//在枚举类中列出枚举值时,就是调用构造器创建枚举类对象
private final String name;//枚举类的成员变量尽量使用private final修饰
//成员变量使用final修饰,必须在构造器里为其指定初始值,因此显式定义带参数的构造器
private Gender(String name) {
this.name=name;
}
public String getName() {
return this.name;
}
}
public class GenderTest {
public static void main(String[] args) {
// Gender g=Enum.valueOf(Gender.class, "MALE");//枚举类的实例只能是枚举值,不是new创建的
Gender g=Gender.valueOf("MALE");//效果同上
System.out.println(g+"代表:"+g.getName());
}
}