一、 包装类
基本数据类型不支持“对象”,没有成员变量,方法可以调用,所以出现包装类。
1.1 自动装箱和自动拆箱
基本类型与包装类对象之间进行转换
自动装箱:基本类型赋值给对应包装类变量,或者给object(子类可赋给父类)
自动拆箱:对应包装类变量赋值给基本类型
public class Primitive2string {
public static void main(String[] args) {
String a = "123";
int b = Integer.parseInt(a); //把字符串数值转换为数值,字符串必须是数字
int c = new Integer(a);
System.out.println(b);
float f1 = Float.parseFloat(a); //123.0 把浮点型数值转换为数值,字符串必须是数字
System.out.println(f1);
String str1 = String.valueOf(123.0f); // 123.0 把float转换成string类型
System.out.println(str1);
String str2 = String.valueOf(123.0d); // 123.0 把double转换成string类型
System.out.println(str2);
String str3 = String.valueOf(true); // 123.0 把boolean转换成string类型
System.out.println(str3); //true
}
}
系统把一个-128–127之间的整数自动装箱成Integer实例,并放入了一个名为cache的数组中缓存起来。如果以后把一个-128–127之间的整数自动装箱成一个Integer实例时,实际上是直接指向对应的数组元素,因此-128–127之间的同一个整数自动装箱成Integer实例时,永远都是引用cache数组的同一个数组元素,所以它们全部相等:但每次把一个不在-128~127范围内的整数自动装箱成Integer实例时,系统总是重新创建一个Integer实例,所以出现程序中的运行结果。
Integer ia = 2;
Integer ib = 2;
System.out.println(ia.equals(ib)); //true
Integer ic = 128;
Integer id = 123;
System.out.println(ic.equals(id)); //false
二、 处理对象
2.1 toString()
toString()方法是Object类里的一个实例方法,所有的Java类都是Object类的子类,因此所有的Java对象都具有toString()方法。
2.2 ==和equals()
Java程序中测试两个变量是否相等有两种方式:
一种是利用
==
运算符,对象类型比较的是地址,基本类型比较的是值
另一种是利用equals()方法。对象类型比较的是值,
public class EqualTest {
public static void main(String[] args) {
String a = "hello";
String b = "hello";
int x =1;
int y =1;
Integer w = new Integer(1);
Integer e = new Integer(1);
String c = new String("hello");
String d = new String("hello");
//使用new String()创建的字符串对象是运行时创建出来的,它被保存在运行时内存区(即堆内存),不会放入常量池中。
System.out.println(w.equals(e)); //true
System.out.println(w == e); //false
System.out.println(x == y); //true
System.out.println(a == b); //true
System.out.println(a.equals(b)); //true
System.out.println(c == d); //false
System.out.println(c.equals(d)); //true
}
}
2.3 "hello’"直接量和new String(“hello”)有什么区别?
当Java程序直接使用形如"hello"的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;
当使用new String(“hello’”)时,JVM会先使用常量池来管理"hello’"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String(“hello”)一共产生了两个字符串对象。
常量池(constant pool)专门用于管理在编译时被确定并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口中的常量,还包括字符串常量。
public class StringCompareTest {
public static void main(String[] args) {
String s1 = "java"; //直接用常量池的值 , 在编译期就确定下来
String s2 = "python";
String s3 = "java" + "python";
String s4 = "java" + "python" + "c";
String s5 = s1 + s2;
String s6 = new String("java"); //运行时创建出来的,它被保存在运行时内存区(即堆内存)内,不会放入常量池中
String s7 = "j" + "a" + "v" + "a"; //直接用常量池的值 , 在编译期就确定下来
System.out.println(s1 == s6); //false
System.out.println(s3 == s5); //false
System.out.println(s1 == s7); //true s1,s7直接用常量池的值,在编译期就确定下来,都将引用常量池中的同一个字符串对象
}
}
三、 类成员
static修饰的类成员属于整个类,不属于单个实例
类变量属于整个类,当系统第一次准备使用该类时,系统会为该类变量分配内存空间,直到该类被卸载。当类初始化完成后,类变量也被初始化完成。
类变量既可通过类来访问,也可通过类的对象来访问。但通过类的对象来访问类变量时,实际上并不是访问该对象所拥有的变量,因为当系统创建该类的对象时,系统不会再为类变量分配内存,也不会再次对类变量进行初始化
静态初始化块也是类成员的一种,静态初始化块用于执行类初始化动作,在类的初始化阶段,系统会调用该类的静态初始化块来对类进行初始化。一旦该类初始化结束后,静态初始化块将永远不会获得执行的机会。
对static关键字而言,类成员(包括方法、初始化块、内部类和枚举类)不能访问实例成员(包括成员变量、方法、初始化块、内部类和枚举类)。因为类成员是属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况,如果允许类成员访问实例成员将会引起大量错误。
3.1 单例类
如果一个类始终只能创建一个实例,则这个类被称为单例类。
根据良好封装的原则:一旦把该类的构造器隐藏起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。
除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故该成员变量必须使用static修饰。
class Singleton{
private Singleton(){} //隐藏构造器
private static Singleton instance; //使用一个类变量来缓存曾经创建的实例
public static Singleton getInstance(){ //提供静态方法,用于返回Singleton实例
if(instance == null){ // null ,不曾创建,不为null,创建了也不会重新创建
instance = new Singleton(); //创建Singleton对象,将其缓存
}
return instance;
}
}
public class SingletonTest {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance(); //构造器被隐藏了,只能通过getInstance来得到实例
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
//Singleton类只能产生一个实例,两次Singleton对象实际是同一个对象
}
}
四、 final修饰符
final关键字可用于修饰类、变量和方法,final修饰变量时,表示该变量一旦获得了初始值就不能被重新赋值。
4.1 final成员变量
成员变量是随类初始化或对象初始化而初始化的。当类初始化时,系统会为该类的类变量分配内存,并分配默认值:当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。
当执行静态初始化块时可以对类变量赋初始值:当执行普通初始化块、构造器时可对实例变量赋初始值。因此,成员变量的初始值可以在定义该变量时指定默认值,也可以在初始化块、构造器中指定初始值。
final修饰的类变量、实例变量能指定初始值的地方如下。
类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
实例变量:必须在非静态初始化块、声明该实例变量****或构造器或普通代码块中指定初始值,而且只能在三个地方的其中之一指定。
public class FinalVariableTest {
final int a = 6; //定义成员变量时指定默认值
final String str;
final int c;
final static double d;
{ //初始化块,可对没有指定默认值的实例变量指定初始值
str = "Hello";
// a = 9; //定义a实例变量时已经指定了默认值,不能为a重新赋值
}
static { //静态初始化块,可对没有指定默认值的类变量指定初始值
d = 5.6;
}
public FinalVariableTest(){ //构造器,可对既没有指定默认值,又没有在初始化块中
c = 5;
}
public void changeFinal(){
// d = 12; //普通方法不能为final修饰的成员变量赋值
}
public static void main(String[] args) {
FinalVariableTest ft = new FinalVariableTest();
System.out.println(ft.a); //6
System.out.println(ft.d); //5.6
System.out.println(ft.c); //5
}
}
4.2 final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。
public class FinalLocalvariableTest {
public void test(final int a){
// a = 5; 不能对final修饰的形参赋值
}
public static void main(String[] args) {
final String str;
str = "a";
// str = "b"; //已经被赋值过
final String str1 = "a";
// str1 = "b";//已经被赋值过
}
}
4.3 final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。
但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用地址不变,即一直引用同一个对象,但这个对象完全可以发生改变。
public class FinalReferenceTest {
public static void main(String[] args) {
final int[] a = {1,2,3,4,5};
System.out.println(Arrays.toString(a)); //[1, 2, 3, 4, 5]
a[0] = 2; //a只是个引用变量,修改值时地址没变
System.out.println(Arrays.toString(a)); //[2, 2, 3, 4, 5]
// a = null; //对a重新赋值,非法
}
}
4.4 可执行“宏替换”的final变量
对一个final变量来说,不管它是类变量、实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。,这就是宏替换。
final String str = "s"; //1.用final修饰,2.定义final变量指定初始值,3.在编译时就确定下来(非调用方法不能编译,那是运行时编译)
System.out.println(str); //相当于System.out.println("s");
Java会使用常量池来管理曾经用过的字符串直接量,例如执行String a="java";
语句之后,常量池中就会缓存一个字符串"java";如果程序再次执行String b="java";
,系统将会让b直接指向常量池中的"java"字符串,因此a=b将会返回true。
public class StringJoinTest {
public static void main(String[] args) {
String s1 = "java"; //编译器时已经确定了
String s2 = "ja" + "va"; //直接读常量池中已经缓存的java字符串,
String s3 = "ja"; //普通变量
String s4 = "va";
String s5 = s3 + s4; //s3和s4只是普通变量,无法在编译时确定s5的值
System.out.println(s1 == s2); //true
System.out.println(s1 == s5); //false
}
}
4.5 final方法
final修饰的方法不可被重写
Java提供的Object类里就有一个final方法:getClass(),用final把这个方法密封起来。但对于该类提供的toString()和equals()方法,都允许子类重写,因此没有使用final修饰它们。
public class FinalMethodTest{
public final void test(){}; //用final修饰了
}
public class Sub extends FinalMethodTest{
//下面方法定义将出现编译错误,不能重写final方法
public void test(){};
}
对于一个private方法,因为它只在当前类中可见,所以子类无法访问该方法一如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。
public class FinalMethodTest{
private final void test(){}; //用private + final修饰了,只在当前类能被访问
}
public class Sub extends FinalMethodTest{
//成功,用private + final修饰了,被当成新方法
public void test(){};
public void test(String s){}; //可以被重载,只是不能被重写
}
4.6 final类
final修饰的类不可以有子类,例如java.lang.Math类就是一个final类,它不可以有子类。
当子类继承父类时,将可以访问到父类内部数据,并可通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可被继承,则可以使用final修饰这个类。
public final class Finalclass{} //final修饰
//下面的类定义将出现编译错误
class Sub extends Finalclass {}
4.7 不可变类
4.8 缓存实例的不可变类
五、 抽象类
5.1 抽象方法和抽象类
抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。
抽象方法和抽象类的规则如下。
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。
抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例。
抽象类可以包含成员变量、方法、构造器、初始化块、内部类5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。
含有抽象方法的类(包括直接定义了一个抽象方法:或继承了一个抽象父类,但没有完全实现父类包含的抽象方法:或实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
public abstract class Shape { //有抽象方法,只能被定义为抽象类
{
System.out.println("执行Shape初始化块...........");
}
private String color;
public abstract double calPerimeter(); //计算周长抽象方法,有抽象方法,只能被定义为抽象类
public abstract String getType(); //返回形状抽象方法
public Shape(){}
public Shape(String color){ //创建子类实例调用
System.out.println("执行Shape构造器...........");
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class Triangle extends Shape{
private double a; //定义三角形三边
private double b;
private double c;
public Triangle(String color,double a,double b,double c){
super(color);
this.setSides(a,b,c);
}
private void setSides(double a, double b, double c) {
if(a >= b+c || b >= a+c || c >= a+b){
System.out.println("三角形两边之和必须大于第三边");
return;
}
this.a = a;
this.b = b;
this.c = c;
}
@Override
public double calPerimeter() { //重写Shape类的计算周长的抽象方法
return a+b+c;
}
@Override
public String getType() { //重写Shape类的形状的抽象方法
return "三角形";
}
}
public class Circle extends Shape{
private double radius;
public Circle(String color,double radius){
super(color);
this.radius = radius;
}
private void setRadius(double radius) {
this.radius = radius;
}
@Override
public double calPerimeter() {
return 2 * Math.PI * radius;
}
@Override
public String getType() {
return getColor()+"圆形";
}
public static void main(String[] args) {
Shape s1 = new Triangle("黑",3,4,5);
Shape s2 = new Circle("黄",3);
System.out.println(s1.getType());
System.out.println(s1.calPerimeter());
System.out.println(s2.getType());
System.out.println(s2.calPerimeter());
}
}
除此之外,当使用static修饰一个方法时,表明这个方法属于该类本身,即通过类就可调用该方法,但如果该方法被定义成抽象方法,则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误)。因此static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。
static和abstract并不是绝对互斥的,static和abstract虽然不能同时修饰某个方法,但它们可以同时修饰内部类。
abstract关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会有方法体,因此abstract方法不能定义为private访问权限,即private和abstract不能同时修饰方法。
5.2抽象类的作用
抽象类不能创建实例,只能当成父类来被继承。
抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给其子类去实现。
父类中可能包含需要调用其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。
六、 接口
6.1 接口定义
接口定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组公用方法。
接口里可以包含成员变量(只能是静态常量)、方法(只能是抽象实例方法、类方法、默认方法或私有方法)、内部类(包括内部接口、枚举)定义。
接口里定义的方法只能是抽象方法、类方法、默认方法或私有方法,因此如果不是定义默认方法、类方法或私有方法,系统将自动为普通方法增加abstract修饰符;定义接口里的普通方法时不管是否使用public abstract修饰符,
接口里的普通方法总是使用public abstract来修饰。接口里的普通方法不能有方法实现(方法体):但类方法、默认方法、私有方法都必须有方法实现(方法体)。
定义接口成员变量时,默认是public static final修饰。如果指定访问控制修饰符,则只能使用public访问控制修饰符。
由于接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。
6.2 接口的继承
接口的继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口。
6.3 接口和抽象类
抽象类 | 接口 | |
---|---|---|
实例化 | 否 | 否 |
继承 | 单 | 单、双 |
方法 | 抽象、非抽象 | 抽象 |
构造方法 | 有 | 无 |
初始化块 | 有 | 无 |
变量 | 有 | 无 |
常量 | 有 | 有 |
七、 内部类
把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类.
内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
.
内部类成员可以直接访问外部类的私有数据,但外部类不能访问内部类的实现细节,例如内部类的成员变量。
.
匿名内部类适合用于创建那些仅需要一次使用的类。
.
内部类与定义外部类还存在如下两点区别。
内部类比外部类可以多使用三个修饰符:private、protected、static一外部类不可以使用这三个修饰符。
非静态内部类不能拥有静态成员。
内部类被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员,局部内部类和匿名内部类则不是类成员。
成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态内部类。
内部类作为其外部类的成员,所以可以使用任意访问控制符如private、protected和public等修饰。
7.1 非静态内部类
public class Cow {
private double weight;
public Cow(){ }
public Cow(double weight){
this.weight = weight;
}
// setter和getter//
public class CowLeg{
private double height;
private String color;
public CowLeg(double height,String color){
this.height = height;
this.color = color;
}
// setter和getter//
public void info(){
System.out.println("奶牛颜色是:"+color+",身高是:"+height);
System.out.println("奶牛体重是:"+weight);
//访问外部类的private方法,内部类可访问外部类private,protected,public等
}
}
public void test(){
CowLeg c1 = new CowLeg(12.0,"红色");
c1.info();
}
public static void main(String[] args) {
Cow c = new Cow(345);
c.test();
}
}
编译上面程序,文件所在路径生成了两个class文件,一个是外部类的class文件Cow.class,另一个是内部类的class文件CowSCowLeg.class,
即成员内部类(包括静态内部类、非静态内部类)的class文件总是这种形式:OuterClasss$InnerClass.class。
在非静态内部类对象里,保存了一个它所寄生的外部类对象的引用(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里)
因此,如果外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定来区分。
public class DiscernVariable {
private String prop = "外部类";
public class Inclass{
private String prop = "内部类";
public void info(){
String prop = "局部变量"; //重名
System.out.println("通过类名+this访问外部类变量:"+DiscernVariable.this.prop);
System.out.println("通过this访问内部类变量:"+this.prop);
System.out.println("直接访问变量:"+prop);
}
}
public void test(){
Inclass i = new Inclass();
i.info();
}
public static void main(String[] args) {
DiscernVariable d = new DiscernVariable();
d.test();
}
}
非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显式创建非静态内部类对象来调用访问其实例成员。
public class Outer {
private String outprop = "外部类";
public class Inclass{
private String inprop = "内部类";
public void info(){
System.out.println("访问外部类变量:"+ outprop);
}
}
public void test(){
// System.out.println("访问内部类变量:"+ inprop); //Cannot resolve symbol 'inprop'
System.out.println("访问内部类变量:"+ new Inclass().inprop); //必须显式创建内部类对象
}
public static void main(String[] args) {
Outer d = new Outer(); //只创建外部类对象,没创建内部类对象
d.test();
}
}
7.1 .1 非静态内部类对象和外部类对象的关条?
非静态内部类对象必须寄生在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄生其中。因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在!而非静态内部类对象访问外部类成员时,外部类对象一定存在。
非静态内部类里不能有静态方法、静态成员变量、静态初始化块,但可以包含普通初始化块。
全部都是错误
public class InnerNoStatic {
public class InnerStatic {
static {
System.out.println("======");
}
private static int in;
private static void test(){};
}
}
7.2 静态内部类
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。
因此使用static修饰的内部类被称为类内部类,有的地方也称为静态内部类。
静态内部类可以包含静态成员,也可以包含非静态成员。
静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。
public class StaticInnerclassTest {
private int prop1 =5;
private static int prop2 = 9;
public static class StaticInnerclass {
private static int age; //静态内部类里可以包含静态成员
private void test(){
// System.out.println(prop1); //静态内部类无法访问外部类的实例变量
System.out.println(prop2); //静态内部类无法访问外部类的实例变量
}
}
}
静态内部类是外部类的一个静态成员,因此外部类的所有方法、所有初始化块中可以使用静态内部类来定义变量、创建对象等。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
public class AccessstaticInnerclass {
static class StaticInnerclass{
private static int pro1 =5;
private int prop2 = 5;
}
private void test(){
System.out.println(StaticInnerclass.pro1);
System.out.println(new StaticInnerclass().prop2);
}
}
7.3使用内部类
7.3.1外部类内部使用内部类
不要在外部类静态成员使用非静态内部类。
7.3.2外部类以外使用非静态内部类
private修饰的内部类只能在外部类内部使用
外部类以外 | 内部类 | |
---|---|---|
public | 任何地方 | |
private | 不能 | 不能 |
protected | 同一包下其他类和外部类子类 | |
default | 同一包下其他类 |
7.3.3外部类以外使用静态内部类
class StaticOut{
static class StaticIn{ //定义静态内部类,一个包中其他类可访问该内部类
public StaticIn(){
System.out.println("静态类的构造器");
}
}
}
public class CreatestaticInnerInstance {
public static void main(String[] args) {
StaticOut.StaticIn in = new StaticOut.StaticIn();
}
}
7.4局部内部类
内部类放入方法内定义,那么该内部类就是一个局部内部类,仅仅在该方法内有效。局部内部类不能在外部类的方法以外使用,因此局部内部类不能使用访问控制符和static修饰符修饰。
public class LocalInnerclass {
public static void main(String[] args) {
class InnerBase{ //定义局部内部类,在方法内定义
int a;
}
class InnerSub extends InnerBase{ //定义局部内部类的子类
int b;
}
InnerSub is = new InnerSub(); //创建局部内部类对象
is.a = 5;
is.b = 8;
System.out.println(is.a +":"+ is.b); //5:8
}
}
编译上面程序,看到生成了三个class文件:LocalInnerClass…class、LocalInnerClassS1InnerBase.class
LocalInnerClass$1InnerSub.class,这表明局部内部类的class文件总是遵循如下命名格式:OuterClassSNInnerClass.class.
局部内部类的class文件的文件名比成员内部类的class文件的文件名多了一个数字,这是因为同一个类里不可能有两个同名的成员内部类,而同一个类里则可能有两个以上同名的局部内部类(处于不同方法中),所以Java为局部内部类的class文件名中增加了一个数字,用于区分。
7.5匿名内部类
适合创建只需要使用一次的类,创建匿名内部类会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。
必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口。
两条规则
匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类。
匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义初始化块,可以通过实例初始化块来完成构造器需要完成的事情。
public class AnonymousTest {
//Product对象传参,但只是接口,无法直接创建对象,要接口实现类对象传入该方法,只用一次就匿名内部类,多次实现类
void test(Product p){
System.out.println("购买了"+p.getName()+",花掉了"+p.getPrice());
}
public static void main(String[] args) {
AnonymousTest at = new AnonymousTest();
at.test(new Product() { //匿名内部类
@Override
public double getPrice() {
return 567.8;
}
@Override
public String getName() {
return "花瓶";
}
});
}
}
等价于
class AnonymousTest implements Product{
public double getPrice() {
return 567.8;
}
public String getName() {
return "花瓶";
}
}
at.test(new AnonymousTest());
当通过使用接口创建匿名内部类时,匿名内部类不能显式创建构造器,因此只有一个隐式的无参构造器,故new接口名后括号不能传入参数值。
当通过继承父类创建匿名内部类时,匿名内部类拥有和父类相似的构造器,拥有相同的形参列表。
创建匿名内部类时,必须实现接口或抽象父类所有抽象方法。如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用final修饰。
八、Lambda表达式
九、 枚举类
实例有限而且固定的类,在Java里被称为枚举类。
9.1 枚举类入门
枚举类是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或者多个接口,也可以定义自己的构造器。
但枚举类终究不是普通类,它与普通类有如下简单区别。
使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类。
使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰
枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰。
枚举类默认提供了一个values()方法,该方法可以很方便地遍历所有的枚举值。
public class EnumTest {
public void judge(SeasonEnum s){
switch (s){
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 (SeasonEnum s : SeasonEnum.values()){ //枚举类默认有个values(),返回全部
System.out.println(s);
}
new EnumTest().judge(SeasonEnum.SPRING); //可通过枚举类.变量使用
}
}
9.2 枚举类的成员变量、方法和构造器
枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以定义成员变量、方法和构造器。
public enum Gender {
MALE("男"),FEMALE("女");
public final String name;
private Gender(String name) { //枚举类构造器只能用private修饰
this.name = name;
}
public String getName() {
return this.name;
}
}
public class GenderTest {
public static void main(String[] args) {
Gender g = Enum.valueOf(Gender.class,"FEMALE");
//通过Enum的valueof()方法来获取指定枚举类的枚举值,此时就是调用构造器
System.out.println(g + "代表: " + g.getName()); //直接访问
}
}
9.3 实现接口的枚举类
枚举类也可以实现一个或多个接口,也要实现接口方法。
9.4包含抽象方法的枚举类
public enum Operation {
plus{
public double eval(double x,double y){return x+y;}
},
minus{
public double eval(double x,double y){return x-y;}
},
times{
public double eval(double x,double y){return x*y;}
},
divide{
public double eval(double x,double y){return x/y;}
};
public abstract double eval(double x,double y); //为枚举类定义一个抽象方法,有不同实现
public static void main(String[] args) {
System.out.println(Operation.plus.eval(3,4));
System.out.println(Operation.minus.eval(3,4));
System.out.println(Operation.times.eval(3,4));
System.out.println(Operation.divide.eval(3,4));
}
}
十、 垃圾回收
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO,等资源)。
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久性地失去引用后,系统就会在合适的时候回收它所占的内存。
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
10.1对象在内存中的状态
public class StatusTranfer {
public static void test(){
String a = new String("java ee"); //定义a,处于可达状态
a = new String("java"); //再次创建字符串对象,此时上面a处于可恢复状态,这个a处于可达状态
}
public static void main(String[] args) {
test();
}
}
一个对象可以被一个方法的局部变量引用,也可以被其他类的类变量引用,当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态; 或被其他对象的实例变量引用,当某个对象被其他对象的实例变量引用时,只有当该对象被销毁后,该对象才会进入可恢复状态。
10.2 强制垃圾回收
两种方式强制回收
调用System类的gc()静态方法:System.gc()。
调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()。
public class GcTest {
public static void main(String[] args) {
for (int i = 0; i < 4; i++){ //创建4个匿名对象,每个创建之后立刻进入可恢复状态,等系统回收
new GcTest(); //此时不会被回收
// System.gc(); //都可以
// Runtime.getRuntime().gc();
}
}
public void finalize(){
System.out.println("系统正在清理GcTest对象的资源。。。。。。");
}
}
10.3 finalize方法
Java提供了默认机制来清理该对象的资源,这个机制就是finalize()方法。当finalize()方法返回后,对象消失,垃圾回收机制开始执行。
finalize()方法具有如下4个特点。
永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用。
finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法。
当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。
public class FinalizeTest {
private static FinalizeTest ft = null;
public void info(){
System.out.println("测试清理资源的finalize方法");
}
public static void main(String[] args) {
new FinalizeTest(); //创建对象立马进入可恢复状态
System.gc(); //通知系统进行资源回收
// Runtime.getRuntime().runFinalization(); 强制垃圾回收调用可恢复对象的finalize方法
System.runFinalization();
ft.info();
}
public void finalize(){
ft = this; //让ft引用到可恢复对象,让可恢复对象重新成可达
}
}
10.4 对象的软、弱和虚引用
10.4.1强引用
一个对象可以被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收
10.4.2软引用
软引用需要通过SoftReference类来实现,当一个对象只有软引用时,它有可能被垃圾回收机制回收。主要看内存空间,内存空间不足时,系统可能会回收它。
10.4.3弱引用
弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用的引用级别更低。当系统垃圾回收机制运行时,不管内存空间够不够,总会回收对象所占用的内存。
10.4.4虚引用
虚引用通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用。
10.4.5引用队列
引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。
联合使用软、弱、引用队列时,系统在回收被引用对象之后,把回收对象对应引用添加到关联的引用队列中。
虚引用在对象被释放之前,把对应的虚引用加到它关联的引用队列中。
public class ReferenceTest {
public static void main(String[] args) {
String str = new String("java"); //创建字符串对象
WeakReference wr = new WeakReference(str); //创建弱引用
str = null; //切断引用
System.out.println(wr.get()); //取出弱引用所引用的对象 java
System.gc(); //强制垃圾回收
System.runFinalization();
System.out.println(wr.get()); //取出弱引用所引用的对象 null
}
}
public class PhantomReferenceTest {
public static void main(String[] args) {
String str = new String("java"); //创建字符串对象
ReferenceQueue rq = new ReferenceQueue(); //创建一个引用队列
PhantomReference pf = new PhantomReference(str,rq); //创建虚引用
str = null; //切断引用
System.out.println(pf.get()); //取出弱引用所引用的对象 null(系统无法通过虚引用来获得被引用的对象)
System.gc(); //强制垃圾回收
System.runFinalization();
System.out.println(rq.poll() == pf); //取出引用队列中最先进入队列的引用与pf进行比较 true
}
}
要使用这些特殊的引用类,就不能保留对对象的强引用:如果保留了对对象的强引用,就会浪费这些引用类所提供的任何好处。
十一、 修饰符的适用范围
native关键字主要用于修饰一个方法,使用native修饰的方法类似于一个抽象方法。与抽象方法不同的是,native方法通常采用C语言来实现。如果某个方法需要利用平台相关特性,或者访问系统硬件等,则可以使用native修饰该方法,再把该方法交给C去实现。一旦Java程序中包含了native方法,
这个程序将失去跨平台的功能。
abstract和final永远不能同时使用;abstract和static不能同时修饰方法,可以同时修饰内部类: