类
类间关系有use-a、has-a和is-a,即依赖、聚合和属于,一般被依赖类以局部变量的形式体现,被聚合类以成员变量的形式体现,属于以继承的形式体现。
访问可见性是指访问者(类)是否有权限访问被访问者(类、方法和域),这里说的类包含接口、enum等。成员的继承性是指成员是否会被子类所继承,成员变量在任何情况下都不会被继承,只有方法才存在被继承的可能性。类可以用public修饰符与默认(没有任何可见性修饰符)两种形式来表达可见性,public修饰的类在所有类中都可见,不带任何修饰符的类对同一个包中的类都可见。成员(域和方法)用private、public、protected和默认(没有任何可见性修饰符)4种形式来表现访问可见性与继承性。
修饰符 | 可见类 | 继承性 |
---|---|---|
public | 所有类 | 可继承 |
protected | 同包类 | 可继承 |
private | 当前类 | 不可继承 |
默认(不带以上任何修饰符) | 同包类 | 不可继承 |
在一个类X中使用另一个类Y的成员(域或方法),需要满足两点,首先是类Y中必须存在该成员(可以是直接显示定义的,也可以是从父类继承的),然后对于类X来说,Y中该成员必须是可见的。
一般用public修饰方法,private修饰数据域,但也不是绝对的,可以用private修饰工具函数,他们只会被本类的方法调用,在修改代码时,如果本类方法不需要调用该函数时,可以将其删掉,由于是私有的,不必担心其他类会调用该函数。
类必须new才会有实体(反射除外),也只有new才会调用构造器,声明类只是声明引用,new操作符就是返回的新建对象的引用。
构造方法的方法名和类名相同,没有返回值(返回值不是void,而是什么也没有),new运算符后自动调用,不能被显式调用,如果没有显式定义任何构造函数,系统会补上默认的无参构造函数。
构造函数不会被继承,构造器可以在函数的第一行调用本类其他或者父类构造器,调用父类的构造器用super(…),调用本类其他构造器this(…),如果不显式调用super(…)或this(…),会自动在构造器的第一条调用super(),就算是默认的构造器,也会在第一行调用super(),这种情况父类必须有无参构造器,否则出错。
所有对象都可以继承到finalize函数,因为它是类Object中定义的protected方法,该方法在对象被回收之前调用,但我们不要依赖于该函数来释放资源,谁也不知道当前对象什么时候被回收,我们可以定义一个方法来手动释放资源,如close方法。比如输入流对象不需要了就需要释放其持有的资源,我们手动调用其close方法就会释放掉资源,如果在finalize中释放资源而不是手动释放,就算流没有用了也可能很久都不会被回收,因为堆区内存满了才会触发垃圾回收,这时资源会一直得不到释放。
this在构造函数中表示正在创建的对象,在其他函数中表示当前对象,this都表示对象,所以在静态方法中不能出现this,因为调用静态方法时可能既没有实体也没有正在创建实体。
super不是表示父类对象,只是为了指示是从父类继承的成员或调用父类构造器而已,不能通过super对一个父类对象赋值。
静态块在类装入虚拟机时执行,普通块在分配实体时执行,多个块依照定义顺序依次执行。
成员变量初始化顺序:将所有成员变量默认初始化,再从前到后依次初始化(直接等号的和{}块中的初始化,两者前面的先初始化),最后调用构造函数。
子类重写的方法不能比父类中的相应方法抛出更多的异常。
子类重写的函数如果只有函数名相同而参数类型不同,不会覆盖从父类继承的函数,会重载。java不能重载运算符。
调用方法时,先根据方法名找到所有重载方法,然后进行参数的精确匹配,如果无法精确匹配,启动自动类型转换找出所有匹配的方法,如果有且仅有一个匹配方法,调用该方法,没有匹配方法编译报错,有多个匹配方法运行出错,java不能重载运算符。
方法返回的如果是数据域的引用,最好返回克隆,不然会破坏封装性,获得句柄的家伙可以通过句柄直接操作数据域,就算数据域是私有的,私有属性只表示外界不能通过实例.的方式操作私有成员,子类重写父类函数返回类型和父类相同或其子类,子类方法不能低于父类或接口相应方法的可见性。
除了基本类型,所有的类型都是Object的子类,包括数组。
尽量重用代码实现一改多改,这要求合理地分解类和工具函数。
静态和final
静态变量用static修饰变量,一个类只有一个备份,可以放在静态代码块中初始化。
静态方法不能用this和super调用,能继承也能被重写,但无法体现多态性。
静态变量和静态方法可以用类/对象.变量或方法进行调用,建议用类.变量或方法,总之,静态属于类,一个类只有一个拷贝,而使用静态变量或方法时,可能并没有创建实体,所以不能用依赖于实体的普通变量和方法来初始化静态变量或在静态方法中调用普通变量和方法,既然一个类只有一份拷贝,那么如果多个线程调用同一个静态方法修改同一个静态变量,那就涉及到线程安全问题。
static代码块static {} 在装入虚拟机时执行一次,一般用于静态变量的初始化,不能访问非静态成员。
在不需要访问类的非static数据域时使用static方法。
final修饰的类为最终类,不能被继承,在不希望类被继承时使用,不能与abstract共存,final类的方法都是final方法,而变量却不受影响。
final修饰的函数不能被覆盖,但可以重载,也就是子类中不能复写函数名和参数全相同的final函数,在希望函数不被重写时使用,static和private方法皆为final。
final修饰变量的不可修改,只能初始化时(非静态成员可以在构造函数中和初始化块中,静态成员可以在static块中)赋值,就算是成员变量也不会自动初始化,他只表示栈区的内容不被修改,也就是说如果修饰对象,那么不能将对象的引用指向另一个对象,但可以改变对象的值。
多态
父类引用指向子类实体,通过父类引用调用父类中已有的成员函数会调到子类中的覆盖函数,如果是调用成员变量,还是会调用到父类的成员变量,如果希望调用到子类的成员变量,可以强转为子类再调用,如Animal a = new Pig(); Animal和Pig都有成员变量age,a.age和((Pig)a).age分别调用父和子的age,通过父类引用不能调用子类新增方法。
抽象类由abstract修饰的类,不能创建实体,但可以声明引用。
抽象方法为abstract修饰的方法,不能有函数体如abstract void fun();抽象方法必须不能是private的,无论抽象类中还是接口中,抽象方法存在都是为了被实现,所以必须要实现类可见,而private子类不可见。
抽象类中的方法不一定是抽象方法,但有抽象方法的类必须被定义为抽象类,所以抽象类的子类如果不被定义为抽象类,就必须实现父类的所有抽象方法。
接口:interface 接口名 {} 接口用来描述类有些什么功能却不实现功能,是为了让别人知道并可以调用实现接口的方法,所以接口的方法和数据都必须是公有的,接口没有实体,所以数据必须是static的,而方法必须是abstract的,这些默认都会自动声明,接口的变量和内部类被自动声明为public static final类型,方法自动被设为public abstract,所以这些关键字都不用写。
class 类名 implement 接口1,接口2{};如果类不是抽象类,那么他必须实现接口声明的所有方法。
接口能够继承接口,甚至多继承,如public interface Mammal extends Animal, wawa{void run();};
接口不能实例化,但可以定义一个引用,然后用实现接口的类对象赋值给他,和抽象类很像,类可以同时继承类并实现接口。
实现接口的类如果不是抽象类,那么必须实现所有接口函数,如果很多类都要实现某一个接口,而许多接口方法的实现都相同,可以写一个抽象类实现接口并实现那些公用方法,要实现接口的类们只需要继承该抽象类即可。
接口用途:实现动态多态性和定义常量组。
接口和抽象类相比,一个类可以实现多个接口,却只能继承一个抽象类;抽象类可以有非抽象方法,接口中必须全部为抽象方法;接口声明的方法全是public,抽象类任意;抽象类有构造函数,接口不能有;接口能够继承接口,甚至多继承;接口方法全是抽象的,会自动在方法前加abstract而抽象类必须显式声明abstract关键字。
内部类
拥有内部类的类会生成Out.class和Out$In.class文件(In为内部类名,当内部类为匿名内部类时,In是一个数字,如果有多个内部内会有多个class文件),总之,一个.java文件可以有多个类,但编译后每个类(包括内部类)都会有一个.class文件。
创建宿主类的时候不会自动创建内部类,非静态内部类的实例默认有一个引用指向其宿主类实例,这就是说非静态内部类的实例不被回收,宿主类的实例无法自动垃圾回收,如果宿主也有引用指向了内部类的实例,两者相互持有就会造成无法垃圾回收,发生内存泄漏。
成员内部类在类块中定义,就像是类的一个成员,既然是成员就要依赖于宿主类的对象,其访问权限修饰符也和类成员一样,private只能在该类中使用该内部类,public可以在别的类中通过宿主类对象使用内部类 如Out.In obj = new Out().new In(); 对于宿主类,内部类的特性和其他类没有区别,但对于内部类,访问宿主类成员就和宿主类的成员函数访问其他成员一样,如果内部类中也有和宿主内一样的成员,默认调用内部类成员,如果想调用宿主类成员,可以通过宿主类名.this.成员,内部类可以访问宿主类的私有成员。
局部内部类定义在外部类的方法之中,作用域只限于定义局部内部类的块,但局部内部类的对象实体生命周期可能很长。局部内部类不能使用非final的局部变量,局部内部类相当于局部变量,不用private、public等修饰。
匿名内部类没有名字,所以在第一次使用后就没法再使用了,但new出的匿名内部类对象可以通过其父类来引用,所以其对象可以多次使用,定义方法如new 类名或接口名(实参列表){…} 不能有构造方法/静态成员(构造方法需要知道类名,静态成员需要通过类名.来引用,而匿名类没有类名),{}里面可以重写或实现接口,然而定义新的方法却没有意义,因为通过其句柄无法引用到新定义的方法,当然如果是工具方法就另当别论了。匿名内部类无法定义构造函数,但却会根据new时的实参列表调用父类的构造器,所以父类必须有同实参列表对应的构造方法,如果是实现的接口,实参列表必须为空。
静态内部类在类块中定义,就像是类的一个静态成员,Out.In obj = new Out.In(); 静态内部类只能访问宿主类的静态成员,要想访问宿主类的非静态成员就要创建宿主类对象,这还是和宿主类的静态成员函数一样。一般在不需要访问宿主类成员的时候用静态内部类,可以将静态内部类作为一个容器,比如一个类的函数想要返回多个值,可以为这个包含多个数据域的对象设计一个静态内部类,静态内部类在一定程度上防止了内存泄漏。
枚举类
通过enum定义的所有枚举类都是Enum的子类,编译器会将用enum定义的枚举编译为class文件,class文件反编译后就是一个继承自Enum的类。我们假设有这样一个普通类CommonClass,另外有一个管理类CommonManagerClass,普通类像一般类那样定义域、构造方法、方法,而CommonManagerClass中定义了许多public static final CommonClass常量,CommonClass的构造函数只对CommonManagerClass可见(也就是说CommonManagerClass中定义的CommonClass对象就是虚拟机中所有的CommonClass对象),那么一个enum类就可以看着这两个类合并为了一个类。
所有enum定义的枚举都是Enum的子类,并且皆为final类,所以枚举都继承自Enum,而不能继承其子类,不允许通过class关键字定义继承Enum的类。
enum MyEnum implements Interface { // 可以实现接口
常量1(构造方法实参列表), 常量2; // 该常量是MyEnum类的一个实体(如果枚举定义中只有常量,后面的分号可以省略,如果是无参构造函数,括号可以省略)
域;
private构造方法 // private说明只能使用预定义的常量
方法
}
enum MyEnum {
ABC, BCD, EFG;
private MyEnum() {};
public void fun() {};
private field;
}
引用常量的方式为:Enum myEnum = MyEnum.BCD;
比较两个Enum是否相等只需要用==,因为在enum中定义N个常量,那么Enum就只会有这N个常量。
java.lang.Enum<E extends Enum<E>> {
private final int ordinal; // 每一个子类定义的常量从0开始分配该值
private final int name; // 常量名称
protected Enum(String name, int ordinal);
public final int ordinal();
public String toString(); // 如返回BCD 。
public final int compareTo(E o); // 比较的是ordinal()
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name); // 如MyEnum myEnum = Enum.valueOf(MyEnum.class, "BCD"),返回Enum子类MyEnum中name为BCD的对象。
}
每一个enum定义的枚举类MyEnum,都有静态方法MyEnum[] values()来返回预定义的所有MyEnum常量,也有静态方法MyEnum valueOf(String name)来获取预定义的名为name的MyEnum常量。MyEnum预定义的枚举常量还可以像普通类一样调用MyEnum中自定义的方法。
枚举集合EnumSet和EnumMap。EnumSet是AbstractSet的子类,只是集合的每一个元素都是同一Enum类型的实例,另外较普通Set多了一些方法(这些方法基本是构建集合的方法)。EnumMap是AbstractMap的子类,只是所有的key都必须是同一Enum类型的实例,没有什么其他的特别方法。EnumSet与EnumMap相对于其他集合都针对其元素或Key为Enum的特殊性,做了性能优化,而且两者都是按ordinal排序的有序集合。
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> {
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType); // 创建一个包含指定枚举类里所有枚举值的EnumSet集合。
public static <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s); // 创建一个其元素类型与指定EnumSet里元素类型相同的EnumSet集合,新EnumSet集合包含原EnumSet集合所不包含的、此类枚举类剩下的枚举值(即新EnumSet集合和原EnumSet集合的集合元素加起来是该枚举类的所有枚举值)。
public static <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> s); // 使用一个普通集合来创建EnumSet集合,要求c集合里面的元素必须是同一Enum类型的实例。
public static <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s); // 创建一个指定EnumSet具有相同元素类型、相同集合元素的EnumSet集合。
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) ; // 创建一个元素类型为指定枚举类型的空EnumSet。
public static <E extends Enum<E>> EnumSet<E> of(E first,E…rest); // 创建一个包含一个或多个枚举值的EnumSet集合,传入的多个枚举值必须属于同一个枚举类。
public static <E extends Enum<E>> EnumSet<E> range(E from,E to); // 创建一个包含从from枚举值到to枚举值范围内所有枚举值的EnumSet集合。
}
枚举实现单例模式,而且不会被序列化破坏单例模式
public enum Singleton {
INSTANCE;
// 外部也可以直接调用Singleton.INSTANCE,而无须该方法
public static Singleton getInstance() {
return Singleton.INSTANCE;
}
类的其他方法
}
可变参数
public void varArgs(类型1 a, 类型2 b, 类型3 … args) { // 调用如type3任意个 varArgs(type1, type2, type3a, type3b, type3c);
// 里面相当于有一个数组变量,类型3[] args,可以尽情地当做类型3数组使用args;
}