抽象类
抽象方法与抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类可以没有抽象方法。
抽象方法和抽象类的规则:抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符修饰,抽象方法不能有方法体;抽象类不能实体化,无法使用new关键字调用抽象类的构造器创建抽象类的实例。抽象类不包含抽象方法也不能实例化;抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口和枚举)5成分。抽象类的构造器不能实例化,只能用于被子类调用;含有抽象方法的类(包括直接定义了一个抽象方法,或者继承一个抽象父类但没有完全实现父类包含的抽象方法;实现一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
abstract class abstractClass{ { System.out.println("abstractClass初始化块"); } String name; //抽象方法 //抽象方法没有方法体,不能用private修饰,抽象方法就是为了重写的 public abstract void test(); public abstract void test2(String name); } public class AbstractClassSon extends abstractClass{ public void test(){ System.out.println("子类重写抽象类不带参数的抽象方法"); } public void test2(String name){ System.out.println("子类重写抽象类带参数的抽象方法,参数值为: " + name); } public static void main(String[] args){ AbstractClassSon acs = new AbstractClassSon(); acs.test(); acs.test2("lilei"); } }
final不能与abstract同时使用,final修饰的不能被重写,抽象类就是为了重写。
abstract不能修饰成员变量,也不能修饰局部变量,没有抽象变量、抽象成员变量的说法;abstract也不能修饰构造器,没有抽象构造器的说法。
static和abstract不能同时使用,没有静态抽象方法。static修饰的方法是类方法,abstract修饰的方法是要被重写的,当抽象类没有被重写无法用一个不存在的方法调用类方法。但abstract和static不能互斥的,它们可以修饰内部类。
abstract关键字修饰的方法必须被子类重写才有意义,因此abstract修饰的方法不能定义为private访问权限。
抽象类不能创建实例,只能当成父类来创建。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。
接口
抽象类是从多个类中抽象出来的模板,如果将这种抽象进行得更彻底,则可以提炼出一种更加特殊的抽象类-接口,接口中不能含有普通方法,接口中所有方法都是抽象方法。Java8进行了改进,允许定义默认方法,默认方法提供方法的实现。
一个接口可以有多个父接口,但接口只能继承接口不能继承类
interface 接口名 extends 父接口 implements 接口1, 接口2.。。
由于接口定义的是一种规范,所以接口不能包含构造器和初始化块。接口里可以包含成员变量(只能是静态变量)、方法(只能是抽象方法、类方法或默认方法)内部类(内部接口、枚举)
接口中所有成员都是public访问权限,接口中定义的静态常量而言,它们是接口相关的,因此系统会自动为这些成员变量添加static 和final修饰符。接口中定义成员变量系统会自动帮你添加public static final修饰符。接口定义的成员变量只能在定义时指定默认值。
public static final int MAX_SIZE = 36E;
接口定义的方法只能是抽象方法、类方法或默认方法,因此如果不是定义默认方法,系统将自动为普通方法添加abstract修饰符。接口中普通方法不能有方法实现,但类方法、默认方法都必须有方法实现。
interface InterfaceClass{ //抽象方法 public abstract void abstractMethod(); //默认方法 default void defaultMethod(){ System.out.println("接口中的默认方法"); } //接口的类方法 static void staticMethod(){ System.out.println("接口中的类方法"); } } public class InterfaceTest implements InterfaceClass{ //冲写接口的抽象方法 public void abstractMethod(){ System.out.println("重写接口的抽象方法"); } public static void main(String[] args){ InterfaceTest it = new InterfaceTest(); it.abstractMethod(); it.defaultMethod(); //staticMethod(); } }
接口完全支持多继承
interface ExtendsInterface{ int i = 10; public abstract void father1Method(); } interface ExtendsInterface2{ int j = 20; public abstract void father2Method(); } interface InterfaceClass extends ExtendsInterface, ExtendsInterface2{ //接口可以多继承 } public class InterfaceTest2 implements InterfaceClass{ public void father1Method(){ System.out.println("father1的抽象方法"); } public void father2Method(){ System.out.println("father2的抽象方法"); } public static void main(String[] args){ InterfaceTest2 it = new InterfaceTest2(); System.out.println("i = " + it.i); System.out.println("j = " + it.j); it.father1Method(); it.father2Method(); } }
一个类使用extends继承,但只能继承一个类;使用implements实现多个接口。实现接口的方法,必须用public访问控制修饰符。
接口不能显式继承任何类,但所有接口类型的引用变量都可以直接赋给Object类型的引用变量。
接口和抽象类共同点:接口和抽象类不能被实例化,他们位于继承树的顶端,用于被其他类实现和继承;接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类必须实现这些方法。
不同点:接口只能包含抽象方法、静态方法、默认方法,抽象类可以是包含普通方法;接口只能定义静态常量,不能定义普通成员变量,抽象类都可以;接口不包含构造器,抽象类可以包含;接口不包含初始化块,抽象类可以;一个类只能有一个直接父类,但可以实现多个接口。
内部类
把一个类放在另一个类的内部定义,成为内部类。
内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许统一包中其他类访问该类;内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员可以互相访问,但外部类不能访问内部类的实现细节;匿名内部类适合用于创建仅需要使用一次的类。
public class outClass{ //定义内部类 }
大部分时候,内部类都被作为成员内部类定义,而不是局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
成员内部类分为两种:静态内部类和非静态内部类,静态内部类使用static修饰
public class InnerClass{ private String name; public InnerClass(String name){ this.name = name; } //内部类 public class NBLClass{ private int age; private String color; public NBLClass(){} public NBLClass(int age, String color){ this.age = age; this.color = color; } public void info(){ System.out.println("age = " + age + " color = " + color); System.out.println("访问外部类private修饰的成员变量,name的值为" + name); } } public void test(){ NBLClass nblc = new NBLClass(18, "green"); nblc.info(); } public static void main(String[] args){ InnerClass ic = new InnerClass("lilei"); ic.test(); } }
把这个类的定义放在另一个类的内部,所以就成了一个内部类,可以使用private修饰符修饰这个类
private class NBLClass{ private int age; private String color; public NBLClass(){} public NBLClass(int age, String color){ this.age = age; this.color = color; } public void info(){ System.out.println("age = " + age + " color = " + color); System.out.println("访问外部类private修饰的成员变量,name的值为" + name); } } public void test(){ NBLClass nblc = new NBLClass(18, "green"); nblc.info(); }
如果外部类成员变量、内部类成员变量与内部类里的方法的局部变量同名,则可通过this、外部类类名.this作为限定区分
public class InnerClass{ private String name; public InnerClass(String name){ this.name = name; } //内部类 private class NBLClass{ private String name; public NBLClass(String name){ this.name = name; } public void info(String name){ System.out.println(name); System.out.println(this.name); System.out.println(InnerClass.this.name); } } public void test(){ NBLClass nblc = new NBLClass("内部类的成员变量"); nblc.info("内部类的局部变量"); } public static void main(String[] args){ InnerClass ic = new InnerClass("外部类的成员变量"); ic.test(); } }
外部类要访问非静态内部类的成员,必须显式创建非静态内部类对象类调用访问其实例成员
public class InnerClass{ private int age = 100; //内部类 private class NBLClass{ private int InnerAge = 200; public void info(){ System.out.println("外部类age的值: " + age); } } public void test(){ //外部类访问非静态内部类必须显式创建内部类的实例,再调用变量 System.out.println("内部类InnerAge的值" + new NBLClass().InnerAge); } public static void main(String[] args){ InnerClass ic = new InnerClass(); ic.test(); } }
非静态内部类对象必须寄生在外部类对象中,而爱不累对象则不必一定有非静态内部类对象寄生其中。如果存在一个非静态内部类对象,则一定存在一个被它寄生的外部类。
Java不允许非静态内部类定义静态成员。非静态内部类不能有静态初始化块,但可以包含普通初始化块。非静态内部类普通初始化块的作用与外部类初始化块的一样。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。
public InnerStaticClass{ private static int i = 100; static class InnerClass{ private static int j = 200; public void test(){ System.out.println("静态内部类只能访问外部类的静态成员: " + i); } }
静态内部类是外部类的一个静态成员,因此外部类的所有方法,所有初始化块可以使用静态内部类类定义变量、创建对象等。
外部类不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问内部类的类成员,也可以使用静态内部类对象作为调用者访问。
public class InnerStaticClass{ static class InnerClass{ private static int j = 100; private static int i = 200; } public static void main(String[] args){ //使用静态内部类类名访问变量 System.out.println(InnerClass.i); //创建静态内部类对象访问变量 System.out.println(new InnerClass().j); } }
Java还允许接口里定义内部类,皆苦定义的内部类默认使用public static 修饰。也就是接口内部类只能是静态内部类。
在外部类中使用内部类和普通类没什么区别,需要注意的是不能在外部类的静态成员中使用非静态内不来,静态成员不能访问非静态成员。
在外部类以外使用非静态内部类,则内部类不能用private修饰,private修饰只能在外部类中使用。
class InnerClass{ class In{ public In(String name){ System.out.println(name); } } } public class OutClass{ public static void main(String[] args){ //外部类以外访问内部类 //非静态内部类的构造器碧玺使用外部类对象调用 InnerClass.In icin = new InnerClass().new In("lilei"); } }
在外部类以外使用静态内部类
class InnerClass{ static class In{ public In(String name){ System.out.println(name); } } } public class OutClass{ public static void main(String[] args){ //外部类以外访问静态内部类 InnerClass.In icin = new InnerClass.In("lilei"); } }
其中new InnerClass.In()这里是类名.静态成员的格式访问静态成员,与非静态内部类创建外部类对象访问的方式不同。
局部内部类仅在该方法中有效,局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符和static修饰符修饰。
public class OutClass{ public static void main(String[] args){ class In{ int a = 10; } //外部类以外访问静态内部类 In i = new In(); System.out.println(i.a); } }
匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口
匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象,因此不允许将匿名内部类定义成抽象类。
匿名内部类不能定义构造器,由于匿名内部类没有类名,无法定义构造器。
interface p{ } public class Test{ public void method(p s){ } public static void main(String[] args){ Test t = new Test(); t.method(new p(){ //匿名内部类 }); } }
当通过实现接口来创建匿名内部类时,匿名内部类也不能显式创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故new接口名后的括号不能传入参数值
如果通过继承父类来创建匿名内部类,匿名内部类将拥有和父类相似的构造器,拥有相同的形参列表。
枚举类
使用enum定义枚举类,一个Java源文件最多只能定义一个public访问权限的枚举类,使用enum定义的枚举类默认继承java.lang.enum类,而不是Object类。使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
如果其含有public构造器,那么在类的外部就可以通过这个构造器来新建实例,显然这时实例的数量和值就不固定了,这与定义枚举类的初衷相矛盾,为了避免这种形象,就对枚举类的构造器默认使用private修饰。如果为枚举类的构造器显式指定其它访问控制符,则会编译出错。 另外,注意枚举类的所有实例必须在其首行显式列出,否则它不能产生实例。
枚举类的构造器只能使用private修饰,枚举类所有势力必须在枚举类第一行显式列出,否则不能产生实例。
public enum EnumClass1{ SPRING, SUMMER, FALL, WINTER; //枚举类的构造器只能使用private修饰 private EnulClass1(){ } public static void main(String[] args){ System.out.println(EnumClass1.SPRING); } }
通过values()方法,返回枚举类的所有实例
enum EnumClass1{ SPRING, SUMMER, FALL, WINTER; } public class Test{ public static void main(String[] args){ for (EnumClass1 ec : EnumClass1.values()){ System.out.println(ec); } } }
枚举类也是一种类,只是比较特殊。但也可以定义成员变量、方法和构造器
enum EnumClass1{ SPRING, SUMMER, FALL, WINTER; //枚举类定义成员变量 public String name; } public class EnumTest{ public static void main(String[] args){ //通过values()方法访问枚举类特定的枚举值 EnumClass1 ec = EnumClass1.valueOf(EnumClass1.class, "SPRING"); ec.name = "春天"; System.out.println(ec.name); } }
enum EnumClass1{ SPRING, SUMMER, FALL, WINTER; //枚举类定义成员变量 public String name; } public class EnumTest{ public static void main(String[] args){ //通过values()方法访问枚举类特定的枚举值 //EnumClass1 ec = EnumClass1.valueOf(EnumClass1.class, "SPRING"); //和上面注释效果是一样的 EnumClass1.SPRING.name = "春天"; System.out.println(EnumClass1.SPRING.name); } }
getter和setter方法
enum EnumClass1{ SPRING, SUMMER, FALL, WINTER; //枚举类定义成员变量 private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } } public class EnumTest{ public static void main(String[] args){ //通过values()方法访问枚举类特定的枚举值 //EnumClass1 ec = EnumClass1.valueOf(EnumClass1.class, "SPRING"); //和上面注释效果是一样的 EnumClass1.SPRING.setName("春天"); System.out.println(EnumClass1.SPRING.getName()); } }
枚举类的实例只能是枚举值,不能new。
枚举类通常设计成不可变类,也就是成员变量值不应该允许改变,这样更安全。建议枚举类成员边浪使用private final修饰。
枚举类实例可以重写方法
enum EnumClass1{ SPRING{ public void setName(String name){ this.name = name; System.out.println("重写了方法"); } }, SUMMER, FALL, WINTER; //枚举类定义成员变量 public String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } } public class EnumTest{ public static void main(String[] args){ //通过values()方法访问枚举类特定的枚举值 //EnumClass1 ec = EnumClass1.valueOf(EnumClass1.class, "SPRING"); //和上面注释效果是一样的 EnumClass1.SPRING.setName("春天"); System.out.println(EnumClass1.SPRING.getName()); }
枚举类可以实现接口
interface interfaceTest{ void method(); } public enum EnumClass2 implements interfaceTest{ SPRING, SUMMER, FALL, WINTER; public void method(){ System.out.println("枚举类实现接口"); } }
不同的枚举值实现接口有不同的表现
interface interfaceTest{ void method(); } public enum EnumClass2 implements interfaceTest{ //这里的枚举值是根据构造器创建的 SPRING("春天"), SUMMER("夏天"), FALL("秋天"), WINTER("冬天"); public String name; private EnumClass2(String name){ this.name = name; } public void method(){ System.out.println("枚举类实现接口"); } public static void main(String[] args){ System.out.println(SPRING.name); //输出春天 } }
有抽象方法的枚举类
public enum EnumClass3{ SPRING{ public void method(){ System.out.println("春天重写抽象方法"); } }, SUMMER{ public void method(){ System.out.println("夏天重写抽象方法"); } }; public abstract void method(); public static void main(String[] args){ SPRING.method(); SUMMER.method(); } }
在枚举类中使用匿名内部类是会产生匿名内部子类的,非抽象的枚举内部类才默认使用final修饰。
对象与垃圾回收
垃圾回收机制只负责回收堆内存的对象,不会回收任何物理资源。
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久性失去引用后,系统就会在合适的时候回收它的内存。
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活,从而导致垃圾回收机制取消。
当一个对象在对内存运行时,根据它被引用变量所引用的状态,可以分为三种:
可达状态,当一个对象创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法。
可恢复状态,如果程序中某个对象不再有任何引用变量引用它,就进入可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象所占的内存,在回收该对象之前,系统会调用所有可恢复对象的finalize()方法进行资源清理。
不可达状态,当对象与所有引用变量的关联都被切断,且系统已经调用所有的对象finalize()方法后依然没有使用该对象变成可达状态,这个对象将永久性失去引用。
系统无法精确控制Java垃圾回收机制,但依然可以强制系统进行垃圾回收,这种强制只是通知系统进行垃圾回收,但系统啥时候来依然未知。
强制垃圾回收机制方式:1、System.gc()静态方法;2、Runtime对象的gc方法,Runtime.getRuntime().gc();这种强制机制只是建议系统进行垃圾回收,系统完全有可能并不立即进行垃圾回收,垃圾回收机制也不会对程序的建议完全置之不理,垃圾回收机制会在收到通知以后尽快进行垃圾回收。
当finalize()方法返回以后,对象消失垃圾回收机制开始执行。
某个失去引用的对象占有极小的内存,系统没有严重的内存需求,因此垃圾回收机制并没有试图回收该对象所占有的内存,该对象的finalize()方法不会得到调用。
finalize()方法特点:1、永远不要主动调用某个对线的fianlize()方法,该方法应交给垃圾回收机制调用; 2、fianlize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当做一定会执行的方法; 3、当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态; 4、当JVM执行finalize()方法出现异常时,垃圾回收机制不会报错,程序会继续执行。

浙公网安备 33010602011771号