《2018.06.14》
1.break和continue可以使用标签跳出多层。
2.使用foreach循环迭代数组元素时,对元素进行修改或赋值只是修改了临时变量,离开循环体这个作用域后,数组的值不会改变。
3.int是基本类型,但int[]是引用类型。
4.数组初始化时不能同时指定长度和初始值,即静态初始化和动态初始化不能同时使用。
5.数组初始化时(即new时),一定要让系统明白它的长度。
6.数组一但初始化,就会占用空间,清空了内容也一样。
《2018.06.15》
1.定义并初始化一个引用变量后,内存中分配了两个空间,一个在栈中,用于存放引用变量,一个在堆中,用于存放引用变量实际指向的变量本身。
2.为了让垃圾回收机制回收一个引用变量实际指向的值,可以将所有指向它的引用变量指向null,这样就切断了所有引用与实际变量(存放在堆中)的联系,使实际变量成为垃圾。
3.int[]类型的值只能是int数值类型,不能再指向其他;同理,int[][]的第二维也只能是int数值。
4.从多维数组是多个一维数组多层指向的底层实现来看,java语言没有多维数组。
《2018.07.12》
1.对象是类的具体存在。
2.类的初始化块总是在构造器执行之前被调用。
3.类里面可以定义零个到多个的构造器、成员变量、方法。都为零个的话,称为空类,没有太大意义。
4.类中的成员定义顺序没有任何影响。
5.类中的各个成员可以相互访问,但是有static修饰的不能访问没有static修饰的成员。
5.1如果确实需要在static方法中访问非static修饰的成员,可以这么得到:new ClassA.func();
6.一般类中的变量叫成员变量(field翻译而来),而属性指的是一组setter和getter方法。
7.构造器的返回值其实是当前类。
8.构造器中不能显示的用return返回当前类的对象,因为构造器的返回值是隐式的。
9.类中的this通常可以省略。(如:出现局部变量和成员变量同名的情况,用this来得到成员变量。)
10.this关键字返回的是对象,而不是类。所以static修饰的方法中不能使用this引用。
10.1this在构造器中总是引用改构造器正在初始化的对象,在方法中引用该对象。
10.2this作为返回值会返回当前对象,使代码更简洁。但是这样会造成实际意义的模糊。
11.Java有一个让人极易“误会”语法,就是它允许使用对象来调用static修饰的成员变量、方法,但实际上这是不应该的。因为static修饰的成员变量、方法属于该类而不属于该实例,所以我们不要使用对象去调用static修饰的成员变量、方法。
《2018.08.22》
1.Java里的方法都不能独立存在,所有方法都必须定义在类里面。
1.1.Java中的方法都只能通过“类.方法”或“对象.方法”来进行调用。
1.2当同一个类中的某个方法调用该类的另一个方法时,若是普通方法,则默认使用this作为调用者,若是静态方法,则默认使用类作为调用者。所以表面看起来是方法独立使用,其实还是用类或对象进行调用。
2.使用static修饰的方法属于类本身,既可以使用类作为调用者,也是可以使用对象作为调用者,但是,使用对象作为调用者时,得到的结果会和使用类调用相同,因为底层依然是使用类进行调用。
3.没有使用static修饰的方法属于该类的对象,只能同归对象进行调用。不同对象调用可能有不同的结果。
4.java里方法参数传递的方式只有一种:值传递。
4.1.值传递的实质:系统开始执行方法时,会对形参进行初始化,就是把实参变量的值赋给形参变量,方法里操作的并不是实参变量。所以函数内的操作只会影响形参这个副本,不会影响到实参本身。
4.2当传递引用类型时,因为引用类型是对象在堆栈中的地址,所以形参中存放的也是地址的副本,所以会操作同一个堆栈中的对象,从而影响到实参本身。注意:就算在函数中将形参赋值为null,函数外的实参依旧能访问到堆栈中的对象,因为形参存放的仅仅是地址。
《2018.08.23》
1.个数可变的形参本质就是一个数组类型的形参,所以调用时既可以传入多个参数,也可以传入一个数组。
2.个数可变的形参只能放在形参列表的最后面,也就意味着个数可变的形参只能有一个。
3.使用个数可变形参的方法比使用数组更简洁。
4.递归方法有一条最重要的规定:递归一定要向已知方向递归。
5.为什么返回值不能作为区别重载的方法?因为假如你调用时没有用变量去接受函数的返回值,那系统就无法判断具体是调用哪个方法。
6.重载时最好不要使用个数可变的形参,会降低可读性。
《2018.09.03》
1.变量分为成员变量、局部变量。成员变量包括类变量、实例变量。局部变量包括形参、方法局部变量、代码块局部变量。
2.成员变量自动默认初始化,局部变量需要显示的初始化,否则无法访问(并不是为null)。
3.成员变量被局部变量覆盖时,可用this(示例变量)或类名(类变量)访问。
4.变量的使用规则应该满足:应尽可能缩小变量的存在范围,能用代码块局部变量的地方,就坚决不用方法局部变量。因为这样做会导致:a.扩大变量的生存时间,导致更多内存开销。b.不利于程序模块的内聚性。
《2018.09.04》
1.Java定义了四个访问控制符:private(当前类访问权限) -> default(包访问权限) -> protected(子类访问权限) -> public(公共访问权限)
1.1外部类只能有两种访问控制符:public和default
1.2一个包中所有的类都不是public修饰,则类名只需是合法的文件名即可。但若存在一个public修饰的类,则文件名必须与这个类同名。
2.Java类中可以在一条package语句或不存在package语句,不存在时该类处于默认包下。
3.使用javac编译时,若加上-d选项,则生成的.class文件会放在与源文件中package相对应的目录结构中。
4.使用java运行.class文件时,若源文件中存在package语句,则命令需要在package对应目录结构的最上层执行。
5.Java的包机制需要两个方面的保证:a.class文件必须放在对应的路径下。b.源文件中使用package指定包名。注意:并不是说只要将类放在对应目录结构下就算是指定包名了。
6.若使用包名加类名去访问某个类,即使当前类的包路径和所访问类的包路径有一部分相同,也要使用完整的包路径,不能省略相同的部分。
7.import语句中的*号只是导入该包中的类,该包的子包中的类并没有被导入。
8.import static 可以导入某个静态成员变量、方法或全部的静态成员变量、方法。
9.java.lang这个包下包含了Java语言的核心类,不需要使用import导入,系统会自动导入。
《2018.09.06》
1.构造器是创建对象的途径,是否意味着对象完全由构造器创建?答案是否定的。当执行new一个对象时,系统会先为这个对象分配空间,执行默认初始化,此时这个对象已经产生,只是外部无法访问。但是可以在构造器中通过this来引用。当构造器执行结束后,会将该对象作为返回值返回,从而让外部程序可以访问到。
2.构造器不一定得是public,只是我们通常希望允许系统的任何地方都可以使用,所以设为public。若使用其他访问限定符,则只能在对应可访问的位置创建对象。
3.若没有手动编写构造器,则系统默认含有一个无参构造器。但假如手动编写了一个构造器,则系统将不再提供默认的无参构造器。比如手动创建了一个public Person(String name);则无法再使用new Person();创建对象。只能通过new Person("oysq");来创建,除非再手动编写一个无参构造器。
4.在构造器重载中,当两个构造器含有相同的代码时,为了满足相同代码不重复写的开发原则,我们可以使用this(name,age);来匹配参数列表调用相应的构造器。注意,this后面没有点号,且该语句只能在构造器中使用,且必须是构造器的第一条语句。
5.继承中,因为子类是特殊的父类,所以父类的范围比子类大,也可以说父类是大类,子类是小类。所以子类可以直接赋值给父类的引用变量而不用做任何强制转换,即向上转型,向上转型由系统自动完成。
6.继承的关键字是extends,因为意思是扩展,而不是继承。从子类是父类的扩展这个角度来看,用扩展比用继承更为恰当。此外,拓展(拓展)和派生(derive)只是从子类和父类的观察角度不同而已,描述的是同一件事。
7.Java摒弃了C++中难以理解的多继承,每个类最多只有一个直接父类,但却可以有多个间接父类。若没有指定直接父类,则默认是java.lang.Object类,所以java.lang.Object是所有类的直接或间接父类。
8.继承中,子类无法得到父类的构造器,只能通过super关键字来访问,且就算不写这行代码,也会隐式的调用父类的无参构造器。也可以使用super来调用父类的构造器,如:super(name,age);注意,super后面没有点号。且该行代码必须出现在构造器的第一行。所以this调用本类其他构造与super调用父类构造不会同时出现。
9.继承中,子类无法继承父类由private修饰的方法和变量,所以就算在子类编写一个一模一样的方法或变量,也不属于重写,只是重新定义了一个新方法。
10.Java的引用变量有两个类型:一个是编译时类型,一个是运行时类型。当编译时类型与运行时类型不一致时,就会存在多态。例如:Father f = new Son();编译时f是Father类型,运行时是Son类型。
11.若不考虑private访问限定符的影响:
a.当父类与子类存在同名的静态、实例变量:子类隐藏父类的同名的静态、实例变量。
b.当父类与子类存在同名的实例变量:子类覆盖(重写)父类的同名实例变量(两同两小一大)。
c.当父类与子类存在同名的静态方法:子类隐藏父类的同名静态方法。
12.方法不能交叉覆盖(重写),变量可以交叉隐藏(变量不存在覆盖)。
13.覆盖和隐藏的区别:
a.被覆盖的实例方法:当子类被强制转为父类后,访问的仍然是子类的方法,即多态。
b.被覆盖的静态方法、实例、静态变量:当子类被强制转为父类后,访问的仍然是子类的方法、实例、静态变量。
14.RTTI(运行时类型检查)只针对覆盖,不针对隐藏。即只有方法重写的时候会出现多态。
15.无论是覆盖还是隐藏,都可以通过super或者类名访问到父类的方法或变量。
16.重载和重写在英语中分别是Overload和Override,面试中经常比较差别,但是这种比较并没有太大意义。重载发生在同一个类或者父子类之间,重写发生在父子类之间。两者除了要求方法同名外,没有太大的相似之处。
17.方法的重写也称为覆盖,遵循“两同两小一大”的规则。“两同”是指:方法名相同,形参列表相同。“两小”是指:返回值类型更小或相同,抛出的异常更小或相同。“一大”是指:子类方法访问权限更大或相同。
18.每个对象或类都会在自己的内存中为从父类中被隐藏的实例变量、或覆盖的实例方法创建一个拷贝。当使用super限定来访问这些实例变量、或实例方法,其实是访问内存中拷贝的那份,而类变量、类方法是属于整个类的,没有做拷贝,因此,要用类名去访问类方法和类变量。
19.正如this不能出现在static修饰的方法中一样,super也不能出现在static修饰的方法中。
20.当访问一个变量或方法时,会从类中,父类中,间接父类中,依次往上寻找,最后还是没有,则编译错误。
21.当创建一个对象时,会从java.lang.Object的构造先执行,依次往下,直到当前类的构造。其中,java.lang.Object只有一个默认构造,且该构造并没有输出任何内容。
《2018.09.18》
1.编译时类型和运行时类型不同时,就可能出现所谓的多态。
2.多态值发生在实例方法上,因为编译时的静态方法被指向的实例方法覆盖了。而静态方法、实例变量、静态变量调用的是还编译时类型对应的值。
3.引用类型在编译阶段只能调用其编译时类型所具有的方法,所以当向上转型时,若父类没有子类的某个实例方法,那么不能调用这个方法,否则编译错误。即:Father f = new Son();时,若Son有printName方法,而Father没有,则f.printName();无法编译通过。若是仍想让这个引用变量调用其不具有的运行时类型的方法,则需要进行强制转换。
4.强制转换需要注意:
a.基本类型只能在数值类型间转换。
b.引用类型只能在具有继承关系的类型间进行转换。否则引发编译时错误。
5.强制转换前应该先用instanceof判断是否可以转换成功,以提高代码的健壮性。instanceof用于判断前面的对象是否是后面的类,或者子类、实现类的实例,是则返回true,否则返回false。注意:instanceof运算符前面操作数的编译时类型与后面的类要嘛相同,要嘛具有父子继承的关系,否则引发编译时错误。
《2018.09.19》
1.继承最大的坏处就是破坏封装,而组合可以在一定程度上补充这种缺陷。
2.为了父类的封装性考虑,在继承中,父类应该遵循以下规则:
a.尽可能隐藏父类的变量 ,用private修饰。
b.不要让子类随意访问,修改父类的方法。父类中辅助其他方法的工具类直接用private修饰。希望被外部调用但不被重写的,用public+final修饰,希望子类重写,但不被外部访问的,用protected修饰。
c.尽量不要在构造器中调用可能被重写的方法,可能会造成空指针异常。
3.如何将一个类设为最终类,不能被继承:a.用final修饰这个类。b.所有构造器用private修饰,构造器无法被继承,则无法初始化,也就无法被继承。直接使用该父类时,需要另外提供一个静态方法进行初始化。
4.到底什么时候需要进行继承呢?a.子类比父类多了其他属性,而不紧紧是某个值与父类不同。b.子类有了新的方法。
5.组合指的是把某个类的对象作为新类的成员变量组合进来,提供新的方法调用这些其他类的方法,以实现在新类中使用其他类的方法。为了不让用户看到组合进来的对象,通常在新类中用private修饰被组合进来的对象。
6.何时用继承?何时用组合?
a.当两个类存在部分抽象和具化的关系时(is-a),用继承。例如:动物和狼。
b.当某个类存在由其他类组合而成的关系时(has-a),用组合。例如:人和胳膊。
《2018.09.25》
1.一个类中可以有多个初始化块,但是这没有任何意义。因为初始化块总是才创建对象时隐式执行,并且是总是按顺序全部执行,还不如合成一个初始化块,提高代码的简洁性和可读性。(当然,静态初始化块和普通初始化块除外,它们是要分开的写。)
2.实际上,初始化块是一个假象,使用javac命令编译java类后,该类中的初始化块会消失,块中的代码会被“还原”到每个构造器中,并且位于构造器代码的最前面,所以先执行初始化块,再执行构造器。
3.当第一次创建对象时,系统需要先进行类的初始化。因此会先进行java.lang.Object类的静态初始化块,再执行java.lang.Object类的静态构造器,然后是子类的静态初始化块,子类的静态构造器,以此类推,直到当前类的静态初始化块,最后是当前类的静态构造器。当静态流程走完后,又开始java.lang.Object类的普通初始化块,java.lang.Object类的普通构造器,一次类推到当前类。
4.由于当静态初始化流程完成后,该类会一直存在JVM虚拟机中,所以第二次创建对象时,就不会再进行静态初始化的流程了。
5.初始化块虽然也是类的一种成员,但是它没有名字,没有标识,因此不能通过类、对象来访问。只能在类初始化或创建对象时隐式的执行。
6.初始化块只能使用static修饰符或没有修饰符。
7.由于初始化块的特殊使用方式,所以它通常被用来提取不同构造器中完全相同、无需接收参数的代码,提高代码质量。
《2018.09.26》
1.JDK1.5以前,将基本数据类型的变量转为包装类需要通过包装类的静态方法valueOf()来实现,包装类转基本数据类型则需要通过包装类提供的实例方法xxxValue()方法来实现。
2.JDK1.5开始,提供了自动装箱和自动拆箱的功能,使得包装类对象和对应的基本数据类型之间可以直接相互赋值。此外,还可以通过包装类提供的valueOf()方法实现String类型和基本类型变量之间的转换,注意,String不是包装类型。
3.JDK1.5开始,包装类型可以和对应的基本数据类型直接比较。但是包装类型之间不可以,因为实际上是两个引用类型,所以只有指向同一个对象时才会返回true。
3.1 但是存在一个例外,Integer类型会把-128~127之间的值缓存起来,放在一个Integer[]数组中,所以当两个在此范围内的Integer变量(前提是值相等)比较时,返回true,因为指向了缓存中该Integer[]数组同一个Integer变量。
3.2 Java7开始为所有的包装类提供了一个静态的compare(xxx val1, xxx val2);的方法来比较大小,注意,该方法的boolean类型的比较中,true>false。
4.toString()方法是一个特殊的方法,用于做“自我描述”。Object类提供的toString()方法返回的是对象实现类的“类名+@+hashCode”,若要真正实现“自我描述”的功能,我们需要自己重写toString()方法。(注意,其实String等类已经做了重写。)
5.Java中的“==”和equals()方法比较的都是对象的地址,只有当地址相同时才返回true,实际应用中都要对equals()方法进行重写,才能根据我们自己的标准来判断是否相等。String类就是重写了equals()这个方法。
6.正确的重写equals()方法应该满足:自反性、对称性、传递性、一致性、非null性。
7."hello"和new String("hello")有什么区别?当直接使用"hello"这样的字符串直接量时,JVM会使用常量池来管理这些字符串。当使用new String("hello")时,会先使用常量池管理"hello"直接量,然后调用String类的构造器创建一个对象,然后将对象存放在堆内存中。换句话说,new String()的方法创建了两个字符串对象。
8.常量池专门用来管理编译时被确定并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口中的常量,还包括字符串常量。
8.1 JVM常量池保证相同的字符串常量只会有一个,不会产生多个副本。
所以:
String str = "helloworld";
String a = "hello";
String b = "world";
String c = "he" + "lloworld";
String d = a + b;//编译时无法确定,不能引用常量池中的字符串
String e = new String("helloworld");
System.out.println(str == c);//返回true
System.out.println(str == d);//返回false
System.out.println(str == e);//返回false
9.Java类只包含5种成员,static可以修饰:成员变量、方法、初始化块、内部类(包括接口、枚举)四个部分,但static不能修饰构造器。
10.对象根本就不拥有对应类的类变量、类方法。通过对象去访问这些类成员只是一种假象,可以理解为:其实系统会在底层转换为通过该类去访问类成员。
10.1 很多语言都直接不允许通过对象去访问类成员。
10.2 即使某个实例为null,它也可以访问所属类的类成员。
11.对于static关键字而言,有一条非常重要的规则:类成员不能访问实例变量。因为类成员在系统第一次准备使用该类时进行初始化,而此时类的实例对象还未进行初始化,若在类成员中访问实例变量,将会引起大量错误。
12.单例(Singleton)类:如果一个类始终只能创建一个实例,则这个类被称为单例类。
12.1单例类的写法:
a.使用private隐藏构造器,使外部不能自由创建该类。
b.使用private+static修饰一个隐藏的类成员变量,用来存放这个唯一的实例对象。
c.创建一个static修饰的类方法,在该类方法中,若b中的变量为null,则调用构造器生成实例对象,并返回该实例对象。不为null则直接返回该实例对象。
如此一来,无论这个类方法被调用多少次,返回的都是同一个实例对象,达到单例的效果。
《2018.09.27》
1.这种说法是错误的:final修饰的变量不能被赋值。应该说:不能被重新赋值。
2.,若成员变量没有指定初始值,将会一直是系统默认分配的0、'\u0000'、fanlse或null(注意,系统分配默认的初始值是在定义成员变量、初始化块和构造器之后执行的),那这些成员变量就失去了存在的意义,所以Java语法规定:final修饰的成员变量必须由程序员显式的指定初始值。
2.1 类变量只能在声明和静态初始化块中指定初始值。
2.2 实例变量只能在声明、非静态初始块和构造器中指定初始值。
2.3 普通的成员方法不能为final修饰的成员变量指定初始值。
3.final修饰的成员变量在显式的指定初始值之前不能直接访问,但是能通过方法来访问,基本上可以断定是Java设计的一个缺陷。
4.final修饰的成员变量在定义时可以指定初始值,也可以不指定,但是一定要显示的指定初始值。
5.final修饰的引用类型变量只是其存放的地址不可更改,即必须始终指向同一个对象,但是对象的内容是可以改变的。这是它与final修饰基本类型变量的区别。
6.当用final修饰一个变量(无论是类变量、成员变量还是局部变量)时,若该变量满足三个条件,则它将不再是个变量,而是相当于一个直接量,编译器会将程序中所有使用了该变量的地方替换成该变量的值。
a.使用final修饰。
b.在定义了该变量的时候同时指定了初始值。
c.该初始值在编译时就可以被确定下来。
例如:final String str = "hel" + "lo";
str == "hello";//将返回true,因为都是常量池中的同一个量。
《2018.10.10》
1.final修饰的方法不可被重写,但是可以被重载。
2.当父类的final方法由private修饰时,子类是可以出现相同方法的,但是这不是重写,而是一个不相关的同名函数。
3.final修饰的函数不可以有子类。
4.不可变类是指创建该类的实例后,这个实例中的实例变量不可改变。
4.1 不可变类的实现方法是适用private final修饰成员变量,由构造器来传入参数进行初始化,且对外只有getter方法,没有setter方法。有必要的话,还可以重写hashCode()方法和equals()方法。
4.2 当不可变类的方法中含有引用类型的变量时,由于只是保证了指向的地址不会变,地址存放的内容是可变的,这将会使得这个不可变类是失败的。为了防止这种方法,我们应该在构造器中对传入的引用类型实例做一个拷贝,并只保存这个拷贝作为成员变量的值。外部访问这个成员变量时,对这个成员变量进行拷贝,将拷贝得到的变量传出去。如此一来,该成员变量指向的地址和该地址保存的值将不可改变。
5.由于不可变类是不可变的,如果可能,应该将已经创建的不可变类进行缓存。
5.1 缓存的时候应该注意控制缓存的实例数量。
5.2 Integer类就是一个不可变类,且也做了缓存,且只缓存-128~127之间的实例值。
5.3 通过new Integer()的方法每次都会生成一个新的实例对象,不会启用缓存,性能较差,而valueOf()方法会启用缓存。所以Java9已经将通过new构建Integer对象的方法标记为过时。
《2018.10.11》
1.抽象方法只有方法签名,没有方法实现。
2.含有抽象方法的类必须定义为抽象类,抽象类不一定要含有抽象方法。
3.抽象类不能被实例,只能被继承,且子类必须实现所继承的抽象类中所有的抽象方法,否则该子类也要定义成抽象类,再被继承。
3.1 final修饰的类不能被继承,修饰的方法不能被重写,所以final和abstract是互斥的,永远不能同时使用。(内部类是例外,内部类可以同时被final和abstract修饰)
3.2 private会导致方法不能被重写,所以抽象方法不能被private修饰,不然抽象方法就没有意义了。
3.3 abstract只能用于修饰类(包括内部类)和成员方法,不能用于修饰类方法、成员变量、局部变量、构造器。
4.抽象类中的构造器不是用来new实例对象的,而是用于被子类实例化时调用。
5.注意,抽象方法和空方法体的方法不是同一个概念。
6.如果编写一个抽象父类,父类提供了多个子类通用的方法,并把一个或多个方法留给子类实现,这就是设计模式中的模板模式。
6.1 这一个或多个通用的方法通常是子类都会有,但是不同的子类具体实现方式不同。
《2018.10.12》
1.抽象类是从多个类中抽象出来的模板,如果讲这种抽象进行得更彻底,就可以提炼出一种特殊的“抽象类” —— 接口(interface)。
1.1接口不使用class关键字,使用interface关键字。
2.接口可以包含的成员比类少了两种:构造器和初始化块。它可以包含:
a.成员变量( public static final ),且它只能是静态常量。无论定义时是否使用 public static final 修饰符,系统都会自动为这些成员变量增加这三个修饰符进行修饰。因此,接口中的 int MAX_SIZE=50; 与 public static final int MAX_SIZE = 50; 是一样的。
b.抽象方法( public abstract )。当接口中的方法不是默认方法、类方法或私有方法时,系统会自动为它加上 public abstract 修饰为抽象方法,且该方法不可以有方法体。
c.默认方法( default )。Java8开始支持的默认方法其实就是实例方法,只是之前Java版本规定了接口中的实例方法不能有方法体,且会自动被系统修饰为抽象方法,所以只能换个名字叫默认方法,且用 default 修饰以示区别。默认方法不能用 static 修饰。由于接口不能实例化,所以默认方法使用接口实现类的实例来调用。
d.类方法( public static )。Java8开始支持接口中有类方法,且类方法必须用 static 修饰。若开发者没有使用 public 修饰,则系统会自动加上去。类方法可以用接口直接调用。
e.私有方法( private/private static )。当接口中的默认方法和类方法有相同的实现逻辑时,必然会考虑将这段逻辑抽取成一个工具类,而工具类是应该被隐藏的,所以Java9就增加了私有方法。私有方法可以使用 static 修饰,也可以不使用,所以私有方法既可以是类方法也可以是实例方法,但它必须使用 private 修饰。
f.内部类、内部接口、内部枚举( public static )。系统自动添加 public static 修饰符。
3.与类继承不同,接口支持多继承,即可以有多个直接父接口。子接口扩展父接口,将获得父接口定义的成员。多继承时,多个父接口排在extends关键字后面,以逗号隔开。
《2018.10.14》
1.接口主要有三种用途:
a.声明引用变量,将接口的实现类赋值给接口。
b.调用接口中的常量。
c.被其他类实现。
2.继承使用的关键字是 extends,而实现使用implements。且接口不仅支持多继承,还支持实现多个接口,也是用逗号隔开。
2.1 当某个类同时实现A接口和B接口时,这个类的对象既可以直接赋给A接口的引用变量,也可直接赋给B接口的引用变量,仿佛这个类同时是A和B的子类,这既是Java提供的模拟多继承。
2.2 一个类可以在继承父类时,同时实现接口。注意,implements 关键字要放在 extends 后面。
2.3 实现只发生在类与接口之间,接口与接口之间只能继承。
3.当一个类实现一个或多个接口之后,它就必须实现这些接口中所有的抽象方法,否则这个类将保留父接口的抽象方法,且必须定义成抽象类。
4.实现接口时,也将获得接口中定义的常量、方法等。所以实现也是一种特殊的继承,只是继承的是一个彻底抽象的类,它更多的是一个规范,不符合现实中继承的含义,所以称之为实现。
5.当实现接口中的抽象方法时,由于抽象方法都是public修饰,而子类的访问权限只能相等或更大,所以抽象方法的实现也都是由public修饰。
6.当一个类实现某一个接口时,它本身也还是一个类,所以它还是能直接赋给Object类型的引用变量。
7.接口和抽象类的共同特点:
a.它们都不能被实例化,只能被其他类实现或是继承。
b.它们都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
8.接口和抽象在设计目的和使用方法上都有区别,更主要是设计目的上的区别。
设计目的上的区别:
a.接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务。对于接口的调用者而言,接口规定了调用者可以调用哪些服务。当一个程序中使用接口时,接口是程序内多个模块间耦合的标准,当多个程序之间使用接口时,接口是多个程序之间通信的标准。所以从某种程度上来讲,接口相当于整个系统的“总纲”,它制定了各个模块应该遵循的标准,所以接口不能经常改变,一但接口被改变,影响将是辐射式的,系统中的很多类都要改变。
b.抽象类体现的是一种模板式设计。抽象类作为多个子类的抽象父类,实现了这些子类的部分共同方法,将有差异的方法留给子类自己实现。所以抽象类可以被当成系统实现过程中的中间产品,当不能当成最终产品,需要被子类进一步完善。
使用方法上的区别:
a.接口不能有构造器和初始化块,抽象类可以含有。但是抽象类的构造器并不是为了创建对象,而是为了让子类调用这些构造器,完成对抽象类的初始化。
b.接口不能含有普通方法,抽象类可以含有。接口的普通方法不能有方法体,且必须声明为抽象方法。但是Java8开始有了默认方法。
c.接口只能定义静态常量,不能定义普通成员变量,抽象类两者都可以定义。
d.一个普通类只能继承一个直接父类,包括抽象类。但是可以直接实现多个接口。此外,抽象类与抽象类之间的继承只能是单继承,接口与接口之间的继承可以是多继承。
9.面向接口编程的示例:
a.简单工程模式:当某个普通类需要组合一个类作为成员,且这个成员类可能存在变动、被其他类替换等情况时,选择组合这个成员类的接口类,再通过编写一个工厂类来指定是哪个类,且返回这个成员类的实例。等到在代码中使用这个普通类时,调用工厂类的方法得到成员类的实例,将该实例传入普通类的构造函数,完成组合。这样的目的是,当组合这个成员类的普通类有很多个时,只要改动工厂类中返回的实现类,所有的类都会改变,做到一改全改。
b.命令模式:在定义方法时使用接口,并将接口对象通过参数传入。在使用方法时将具体的实现类当做参数传入,这样就可以同一个方法,含有不同的处理流程了(即所谓的含有不同的“代码块”)。
《2018.10.23》
1.内部类(又称嵌套类),包含内部类的类被称为外部类。
1.1 内部类适用于那些独立存在没有任何意义、属于某个类的一部分、一般只需要用到一次的类。
1.2 内部类可以定义在类的任何地方,定义在成员列表的为成员内部类,定义在方法中的称为局部内部类。局部内部类和匿名内部类不是外部类的成员。
1.3 因为外部类的上一级程序单元是包,所以它只能用public修饰或不使用任何修饰符(即包访问权限)。而内部类的上一级程序单元是外部类,所以可以有4中访问控制权限。
2.成员内部类分为:非静态内部类,静态内部类。
3.非静态内部类的对象里,保存着一个它所寄生的外部类实例的引用。
3.1 当非静态内部类的方式访问某个变量时,现在方法内寻找,然后在该非静态内部类中寻找,最后到外部类寻找,还没有将出现编译错误。
3.2 当外部类的成员变量与内部类的成员变量同名时,使用 外部类名称.this.变量名 访问外部类变量,用 this.变量名 访问内部类成员变量。
3.3 非静态内部类可以访问外部类的成员,因为非静态内部类含有所寄生的外部类实例的引用,而外部类没有该非静态内部类的引用。还有一个原因是,非静态内部类必须寄生在外部类的对象里,而外部类对象则不一定需要有内部类寄生在其中,所以会有可能实例化了外部类,却没有实例化非静态内部类,导致访问非静态内部类时出错。
3.4 外部类想访问内部类的成员,需要实例化一个内部类出来调用,且创建外部类时系统不会自动创建内部类,需要自己手动创建。
4.非静态内部类不能含有静态方法、静态成员变量,静态初始化块。
5.静态内部类用static修饰,属于类本身,有的地方称为类内部类。
6.静态内部类既可以包含静态成员,也可以包含非静态成员。
7.因为静态内部类属于类,所以不能访问到外部类的非静态成员。但是外部类可以通过内部类的类名或内部类的实例来访问静态内部类的类成员。
8.接口中只能定义静态内部类,因为系统会默认使用 pubic static 修饰它。
9.接口内也可以定义内部接口,但是意义不大,因为接口本来就是一个公共规范,暴露给大家使用的,定义内部接口没有什么意义,很少应用到。
《2019.1.5》
1.在外部类以为也可以使用不是private修饰的内部类。
1.1 定义静态和非静态的内部类:Out.In varName;
1.2 定义非静态内部类:Out.In varName = new Out().new In("小张", 26);
1.3 定义静态内部类:StaticOut.StaticIn varName = new StaticOut.StaticIn();
2.非静态内部类(In)的子类(SubIn)也必须持有其父类的外部类(Out)的引用,差别是创建In的时候必须使用外部类Out的实例对象来调用new关键字,而创建SubIn时使用Out对象的实例来调用In的构造器。
3.内部类无法被外部类的子类重写。因为内部类的类名其实是由外部类的类名和内部类的类名组合而成,因此,子类中的内部类与父类中的内部类不可能完全同名,不同命则无法重写。
4.局部内部类的作用域是所在的方法,与其他程序单元相互之间无法访问,所以局部内部类不能使用访问控制符。(所有的局部成员都是如此。)
5.局部内部类生成的class文件名比成员内部类的class文件名多了一个数字,因为局部内部类可能会重名(处于不同的方法中),所以用数字类区分。
7.局部内部类是一个很“鸡肋”的语法,因为它的作用域太小了,只能在当前方法中使用。大部分时候定义一个类都是希望能多次复用,因此实际开发中很少使用局部内部类。
8.匿名内部类适用于创建只需要使用一次的类,必须且最多继承一个类,或必须且最多实现一个接口。
8.1 创建匿名内部类时,会立即创建一个实例,因此不允许将匿名内部类定义成抽象类。且该类会立即消失,无法重复使用。
8.2 匿名内部类没有类名,所以无法定义构造器,但可以有初始化块。
9.匿名内部类在 Java 8 之后进行了改进,这个功能被称为“effectively final”,意思是被匿名内部类访问的局部变量,可以不使用final修饰了,以前是必须要的,但是还是要按照final修饰的语法进行:不能重新赋值。
《2019.1.7》
1.枚举类与普通类的区别:
a.枚举类继承了 java.lang.Enum 类,且不是系统默认的 Object 类,所以它无法再继承其他类。
b.非抽象的枚举类默认使用 final 修饰,无法再派生子类;抽象的枚举类默认使用 abstract 修饰。
c.枚举类和普通类一样,可以实现一个或多个接口。
d.枚举类的构造器默认且只能使用private修饰,参数值在列举实例时就要直接传入。
e.枚举类的所有实例(即所有的枚举值)都要在第一行就显示的列出,且此后再也不能产生新的实例(即只能使用列举的实例,不能再自己去new新的实例)。
2.switch的控制表达式可以是枚举类型,且case表达式的值可以直接使用枚举的实例(如:SPRING),无需再添加枚举类做限定(如:SeasonEnum.SPRING)。
3.compareTo() 方法比较的是枚举实例在第一行列举时的顺序。
4.name() 方法和 toString() 方法返回的是实例的名称,而 valueOf() 方法返回的是实例。
5.枚举类的每个枚举值(即实例)都可以有自己的成员变量和方法,各个实例之间可以相同,也可以各不相同。
5.1 因为枚举类通常希望是不可变类,所以成员变量使用 private final 修饰,并通过构造函数传入赋值是比较简洁安全的。相比提供接口给外部调用赋值要好很多。
5.2 直接实现的函数会出现所有枚举实例的函数体相同,如果希望每个实例的实现不一样,可以通过在枚举类中定义一个抽象方法,使枚举类变成抽象类,这样该枚举类将不再使用 final 修饰,而是使用 abstract 修饰(系统自动添加abstract关键字,手动添加会报错),就可以派生子类完成多态了,然后再通过匿名内部类的方式去为每个实例提供抽象方法的实现。
《2019.01.19》
1.垃圾回收机制具有如下特点:
a.垃圾回收机制只负责回收堆内存的对象,不会回收任何物理资源。如:数据库连接、网络IO等资源。
b.程序只能控制一个对象何时不再被任何引用变量引用,绝对不可能控制它何时被回收。只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃圾回收。不过程序可以主动通知系统进行垃圾回收,但也只是通知,系统是否进行回收依然不确定,只不过大部分时候都会有效果。
c.在垃圾回收任何对象之前,总会先调用它的 finalize() 方法。该方法有可能使对象从可恢复状态变回可达状态。
2.一个对象在堆内存中被引用变量引用的状态有三种:可达状态、可恢复状态、不可达状态。只有当一个对象处于不可达状态时,系统才会真正回收该对象占有的资源。
3.通知操作系统进行回收的方法有两种,效果一样:
a.System.gc();
b.Runtime.getRuntime.gc();
4.强制调用finaloze()的方法有两种,效果一样:
a.Runtime.getRuntime.runFinalization();
b.System.runFinalization();
5.finalize()方法时定义在Object类里面的实例方法。任何Java类都可以重写它,在该方法中进行资源清理。
6.由于程序可能至始至终都没有进行垃圾回收,所以 finalize() 方法可能永远不会被调用到。
7.永远不要主动 finalize() 方法,把它交由垃圾回收机制调用。
8.当JVM执行 finalize() 方法时出现异常,垃圾回收机制不会报告任何的错误,程序继续执行。
9.Java语言对对象的引用有4种方式:强引用、软引用、弱引用、虚引用。后三种都都包含一个 get() 方法,用来获取所引用的对象。
a.强引用即为即为常见的引用方式,引用的对象不可能被回收。
b.软引用通常用于对内存敏感的程序中。
c.弱引用级别比软引用更低,当立即回收机制运行时,不管内存是否足够,都会被回收。然而因为垃圾回收何时运行不可控,所以并不是说当一个对象只有软引用时,就会立刻被回收。
d.虚引用主要用于跟踪对象被垃圾回收的状态。虚引用必须和引用队列(ReferenceQueue)联合使用。
10.为什么要使用软、弱、虚引用:因为使用这些引用类可以避免在程序执行期间将对象滞留在内存中,垃圾回收机制可以随意的释放对象。如果希望尽可能的减小程序运行期间占用的内存,这些引用类就很有用处。但是切记,使用这些引用类时,不能保留对象的强引用,否则会浪费这些引用类所提供的任何好处。
11.注意:String str = "Hello World.";这种操作系统会使用常量池来管理这个字符串直接量,即使用强引用来引用它,所以它不会被回收。
12.由于垃圾回收的不确定性,所以当你从软、弱引用中取出对象时,它可能已经被释放,所以每次取出都要先判断是否为null,是则重新创建,创建方法有两种,其中一种逻辑上存在一定的问题。
13.strictfp 是精确浮点的意思。就是的类、接口和方法将会完全依照IEEE-754来执行,浮点运算更精确。
14.native 修饰的方法类似于一个抽象方法,只是该方法将会用C语言实现。通常用于需要利用平台的特性,或访问系统的硬件等。一旦Java程序包含了native方法,则这个程序将失去跨平台功能。
15.修饰符互斥举例:
a.abstract 和 final 永远不能同时使用。
b.abstract 和 static 不能同时修饰方法,可以同时修饰内部类。
c.abstract 和 private 不能同时修饰方法,可以同时修饰内部类。
d.private 修饰的方法不能被重写,此时再使用 final 已经没有意义了,虽然符合语法。