封装:形式上是将数据和行为组合在一个包中,并对对象的使用者隐藏数据的实现方式。
构造器用来构造和初始化对象。
Date deadline; //定义了一个对象变量,可以引用Date对象,这里没有引用任何对象
deadline是变量,不是对象。由于目前没有引用对象,所以执行deadline.toString会编译错误。
deadline=new Date();
Date birthday=deadline; //这两个变量引用了同一个对象
一个变量并没有实际包含一个对象,而是仅仅引用一个对象。Java的对象变量可以看做是C++中的指针,所有的java对象都存储在堆中,当一个对象包含另外一个对象变量时,这个变量依然可以认为是指向另外一个堆中的对象的指针。
不要编写返回引用可变变量的访问器方法。
class Employee{
private Date hiredate;
Date getHiredate(){
return hiredate;
}
}
Employee harry=...;
Date d=harry.getHireDate();
d.setTime(......); //变量d所指向的Date(也就是harry所指对象的私有域hire达特)发生了改变
如果需要返回可变对象的引用,那么应该使用clone。
class Employee{
private Date hiredate;
Date getHiredate(){
return (Date)hiredate.clone();
}
}
方法可以访问其所在类的私有特性
boolean equals(Employee other){
return name.other.name;
}
类的final实例域必须保证构造器执行完之后它已被初始化,且以后不能对其修改。
calss Employee{
private final String name;
}
final一般修饰基本数据类型或者不可变类的域。对于可变的类,final修饰可能引起混乱。例如:private final Date hiredate; 这仅仅意味着hiredate引用的对象不能改变,而并不意味这这个对象的状态不能改变,类中的任何方法都可以调用hiredate.setTime()来修改hireDate的状态。
静态域属于类的,不属于任何对象。private static int nextId=1;
静态变量使用较少,但静态常量使用比较多。public static final double PI=3.1415;
System.out就是一个静态常量。System类内部有public static final PrintStream out=......;
静态方法是类方法,它不能操作对象,不能访问除静态域以外的实例域。
java中方法的参数传递采用值调用进行的。对象变量传递参数实际是将实参(指针)赋值给形参。
如果构造器、初始化块等地方没有初始化变量,那么变量会自动赋值为0,false,null。
如果没有构造器,那么系统会构造一个默认构造器,进行默认初始化。如果类中有构造器,但是没有提供默认构造器,那么调用e=new Employee();会出错。
调用同一类中另外一个构造器用this(... ...);调用父类中的构造器用super(... ...);
java初始化顺序:父类静态变量—>父类静态代码块—>子类静态变量—>子类静态代码块—>父类非静态变量—>父类非静态代码块—>父类构造方法—>子类非静态变量—>子类非静态代码块—>子类构造方法。
静态代码块会在类第一次加载时进行初始化。以下代码输出:2B31CA31CA
public class F { public F(){ System.out.print("1"); } static{ System.out.print("2"); } { System.out.print("3"); } }
public class S extends F{ public S(){ System.out.print("A"); } static{ System.out.print("B"); } { System.out.print("C"); } }
public class FSTest { public static void main(String[] args){ F t=new S(); t=new S(); } }
继承示例:
public class Employee {
private double salary;
public Employee(double salary){
this.salary=salary;
}
public double getSalary(){
return salary;
}
public void setSalary(double salary){
this.salary=salary;
}
}
public class Manager extends Employee{
private double bonus;
public Manager(double salary,double bonus){
super(salary);
this.bonus=bonus;
}
@Override
/*这里不能用salary+bonus,因为Manager没有显示的域salary,也不能直接访问父类的私有
* 域salary,只有Employee类的变量所指的对象才能访问salary;也不能用getSalary()
* +bonus,这样调用的不是Employee类的getSalary而是Manager类的getSalary(自己的)
* ,这样会不断调用自己的方法;用super调用父类的方法
*/
public double getSalary(){
return super.getSalary()+bonus;
}
@Override
public void setSalary(double salary){
super.setSalary(salary);
}
public double getBonus(){
rreturn bonus;
}
}
public class EmployeeTest {
public static void main(String[] args) {
Employee boss=new Manager(5000,1000);
System.out.println(boss.getSalary());
boss.setSalary(7000);
System.out.println(boss.getSalary());
}
}
如果子类的构造器没有显式的调用父类的构造器,那么子类会自动调用父类的默认构造器。如果父类没有默认构造器,且子类没有显式的调用父类的构造器,那么编译出错。
this引用隐式参数,调用该类的构造器;
super调用父类方法,调用父类构造器。
覆盖一个方法时,子类方法的可见性不能低于父类方法的可见性。父类为public,子类必须是public。
Employee boss=new Manager(5000,1000);
boss.getSalary(); //得到6000
这里boss声明为Employee类但是实际引用了Manager类,在执行时它会调用Manager类的getSalary犯法。
一个对象变量可以引用多种类型的对象的现象称为多态。运行时它能自动选择调用哪个方法,这种现象称为动态绑定。
Manager boss=new Manager(5000,1000); Employee staff=boss; //Employee类型变量staff指向了boss对象,staff和boss指向同一个变量
可以调用boss.getSalary,但是不能调用staff.getBonus,因为编译器认为staff是Employee变量。
调用staff.getSalary时会动态绑定到Manager类的getSalary方法。
Manager m[]=new Manager[10];
Employee s[]=manager; //这个是合法的,因为每个m[i]是Employee对象。
s[0]=new Employee(... ...); //编译器接受了,s[0]是Employee变量
但是,s[0]和m[0]指向了同一个对象像,这导致m[0]指向了一个Employee对象,当调用m[0].getBonue()会出错 。
所以,对于数组类型的数据,要牢牢记清楚自己的类型,插入元素时候必须要保证兼容性。如果将Employee对象插入m[0]所指向的位置时候应该抛出异常。
动态绑定:
编译器查看对象的声明类型和方法名。通过声明类型找到方法列表。
编译器查看调用方法的参数列表。(方法的名称和参数列表称为方法的签名)。
如果方法是private、static、final或者构造器,编译器就可以确定调用那个方法。这就是静态绑定。
否则就要使用运行时(动态)绑定。采用动态绑定意味着:虚拟机将调用对象实际类型的方法。
动态绑定的过程:虚拟机提取对象的实际类型的方法表;虚拟机搜索方法签名;
调用方法。在多态的情况下,虚拟机可以找到所运行对象的真正类型。
一个类用final修饰后就不能被扩展。方法被final修饰就不能就不能被覆盖。
类型转换的目的是在忽略对象的实际类型后,使用对象的全部功能。类型转换只能限定在继承层次内进行。在使用转换之前应该用instanceof进行类型的检查。instaceof只会返回false而不会抛出异常。
if(staff instaceof Manage){
boss=(Manager)staff;
}
抽象类的成员可以具有访问级别,而接口的成员全部public级别
抽象类可以包含字段,而接口只能定义常量
抽象类的成员可以具有具体实现(非抽象方法),而接口不行
抽象类可以有构造方法,接口中不能有构造方法
抽象类中可以包含静态方法,接口中不能包含静态方法
一个类可以实现多个接口,但只能继承一个抽象类
访问级别:
private——仅仅本类可见
public——对所有类可见
protected——对本包及子类可见
默认——对本包可见
所有类的超类Object:
1、Object类型的变量可以引用任何类型的对象,例如Object obj=new int();
2、equals方法。原型是public boolean equals(Object obj){this==obj;}
class Employee{
... ...
public boolean equals(Object other){ //注意啊,这里参数是Object
if(this==other) return true; //两者地址相同
if(other==null) return false; //参数地址为空
if(getClass!=other.getClass()) //两者类型不同
return false;
Employee ot=(Employee)other;
return name.equals(ot.name)&&salary==ot.salary;
}
}
class Manager extends Employee{
.... ...
public boolean equals(Object other){
if(!super.equals(other)) return false; //作为Employee必须先相等
return bonus==((Manager)other).bonus;
}
}
3、hashCode方法。散列码是对象导出的一个整数值,散列码没有规律。equals方法与hashCode方法必须保持一致:x.equals(y)为true,那么x、y的hashCode()应该一样。String的hash如下:
int hash=0;
for(int i=0;i<length();i++)
hash=31*hash+charAt(i);
4、toString方法,用来返回字符串,方便对象之间的+操作。
5、clone方法。它是Object类的protected方法。对象拷贝是让两个变量引用同一个对象,任何一个变量都可以引起对象的状态改变。对象克隆是将一个变量引用的对象的副本让另外一个变量来引用。克隆分深克隆和浅克隆。默认是浅克隆,也就是将对象的状态(全部数据域)拷贝给副本。
但是如果对象包含了对象的引用,当这种对象是不可变的,不会有任何问题,但是如果这种对象是可变的,那么,这会导致两个变量所指的对象的引用型数据域指向了同一个可变对象。这是就应该进行深克隆了,这需要自己进些覆盖重写clone方法(实现Cloneable接口)。
接口用来描述类有什么功能,而不具体实现每个功能。接口不是类,而是类的一组需求的描述。一个类可以实现多个接口。接口中的所有方法自动属于public,因此接口声明中不必提供关键字public。接口中能够定义常量。接口不能有实例域,也不能在接口中实现方法。
接口不是类,所以不能用new来操作。但是可以声明接口型的变量,这个变量必须引用实现了接口的类对象。 Comparable x=new Emplyoyee();
多个无关的类可以实现同一个接口,一个类可以实现多个接口。通过接口可以了解对象的交互界面,而不需要了解对象的类。
可以用instanceof检查一个对象是否属于某个特定的类,也可以用instance检查一个对象是否实现了某个特定的接口: if(anObj instanceof Comparable){... ...}
接口可以扩展。接口可以有常量,不能有实例域或者静态域。
public interface Movable{
void move(double x,double y);
}
public interface Powered{
double milesPerGallon();
double SEED_LIMIT=95; //它会自动变成public static final型的
}
内部类方法可以访问该类定义所在的作用域中的数据,包括私有域。
内部类可以对同一个包中的其他类隐藏。
当定义一个毁掉函数且不想编写大量代码时,可以使用匿名内部类
public class A {
private int s=111;
private class B{
private int s=11;
public void show(int s){
System.out.println(s);//局部
System.out.println(this.s);//内部类
System.out.println(A.this.s);//外层类
}
}
public static void main(String[] args){
A a=new A();
A.B b=a.new B(); //内部类的引用方式:OuterClass.InnerClass
b.show(1);
}
}
局部内部类不仅可以完全的隐藏自己,还可以访问局部变量,不过局部变量要声明为final型的。如果一个内部类不用命名,就可以使用匿名内部类。
public void start(int interval, final boolean beep){ //内部类可以使用beep,不能用interval
ActionListener listener=new ActionListener(){
public void actionPerformed(ActionEvent e){
Date now=new Date();
System.out.println("time:"+now);
if(beep) //内部类使用beep
Toolkit.getDefaultToolkit().beep();
}
}
Timer t=new Timer(interval,listener);
t.start();
}