《疯狂java讲义》笔记 1-5章
1.编译语言和解释语言理解,摘自李刚老师的《疯狂Java讲义》第三版:
就是说,Java和.net都是编译型有事解释型语言。编译型就是根据不同平台编译成不同的可执行机器码,编译过程中会进行很多的优化,比如:字符串自动连接以及各种常量替换和连接多个文件生成一个文件等等,而解释型只需要能被解释器解释就可以了,而不需要编译,比如说:html是解释型语言,不需要经过编译,是什么浏览器就解释什么。其实感觉二进制码也是一种解释型语言,而cpu就充当解释器。java和.net都需要虚拟机(也可以认为是解释器)来运行,先都是经过编译器编辑成VM能解释的中间语言(期间会进行很多优化和各种连接,其实都是为了方便程序员写代码,减少工作量或者说是便于程序源代码维护而屏蔽的工作,都由编译器后台编译来帮程序员完成的工作,比如:常量的替换,在编译的时候,凡是使用到常量的地方,编译器都会直接用常量的值直接替换上去,跟变量不一样),然后执行的时候,中间语言就被VM直接解释成当前平台的机器代码(毕竟不同平台的机器码肯定是不一样的),最后cpu再解释机器码(这句可以忽略不看)。所以说java和.net是半编译半解释型语言。
2.对象初始化
①引用对象可以直接声明同时使用{}初始化,例如:
1 var stu = new Student() 2 { 3 Name = "", 4 StuNo = "20102100104" 5 };
而java这样是不行的,java要像以下这样才能初始化(或者定义一个带参数的构造器来初始化):
Student stu = new Student(); stu.Name = ""; stu.StuNo = "20102100104" ;
并且java不可以使用var这种自动判断类型的功能,比.net弱多了。
2.java和.net不会为局部变量默认初始化,局部变量必须先初始化才能使用,不然编译不过,但是会为成员变量默认初始化(int默认为0,bool默认为false,引用类型默认为null……)。
3.java没有虚函数的概念,而.net有虚函数的概念(函数前面加上关键字Virtual)。因为java里面的函数默认全部都是Virtual函数,也只能是Virtual函数,估计是为了突出java面向对象的思想,毕竟面向对象很重要的一个特点就是“多态“,但是这样强制加上去反而不够灵活了,用父类引用调用方法,如果父类,子类都有同一个方法,这时候,在java里面就调用不了父类的方法了,而.net中只要该方法不是Virtual函数的话,调用的就是父类的方法。因为在.net中,如果不是Virtual函数的话,是根据引用变量类型来决定调用的方法的,在编译时期就已经明确,而Virtual方法是根据引用变量指向的对象实例来决定调用的方法的,是在执行的时候才能确认调用的方法。
4.Java添加函数注释是输入“/**”,然后回车,而.net添加函数注释是输入"///"按回车,如下:
.net
/// <summary> /// create by ismallboy /// </summary> class Program
java
/** * * @author ismallboy * */ public class ScopeTest {
顺便再说几个很好用,但是很多人不知道的快捷键:
shift+Enter:光标移到下一行,不管你当前光标在当前行的哪个位置,这个快捷键当真好用,而且经常用到,不用每次都要用鼠标点或者移动键盘的向下方向键;-----------------------推荐
Ctrl+D(android studio是Ctrl+X):删除当前行
……其他有待发现,也希望园友补充
5.Java基本数据类型,个人感觉里面的表格分类分得真心好,故直接Copy过来(图截取李刚老师的《疯狂Java讲义》第三版)
6.关于字符集的知识,个人感觉李刚老师的解释也比较到位,当然详细自己可以查找资料去好好理解,要理解好编码格式和解码格式,计算机都是只能保存二进制的内容的,就是用二进制来保存各种各样的数据,比如:多媒体,文字,图片等等,都是按照一定的编码格式编码,然后计算机只是保存该种编码格式的编号的二进制格式,解码的时候,也就是你要看的时候,也只能按照编码的时候的那种编码方式(或者兼容编码的时候的编码格式的其他编码方式也行,有点拗口,原谅我语文是数学老师教的)来解码才能正常显示,不然看到的也就是一坨的乱码而已,其实也就是把计算机保存的二进制转成编码表上面的编号,找到对应的字体显示出来而已,当然了这只是说文字,多媒体也有自己的编码格式,自己找资料理解了,如下(图截取自李刚老师的《疯狂Java讲义》第三版)
7.数组,也是一种类型,而且是一种引用类型,以前个人很不喜欢int[100] a;这种定义方式,因为以前c++教材是主张试用int i[100] ;这种方式的,发现java里面主张这种方式一时还不是很习惯,看了《疯狂java讲义》里面的讲解,说可以将int[]视为一种类型,就像String一样看待,这突然就豁然开朗,觉得很好理解了,int[] i;的意思就是定义了一个int[] 类型的i变量,你看,多好理解,二位数组的话就int[][],也把他看作一种类型,int[][] a;就是定义了一个int[][]类型的变量,的而且李刚老师说,java里面都是一维数组,不存在多位数组,解释的也很有道理,mark。
8.形参个数可变的方法,在java中是在方法的最后一个形参的类型后面增加三个点(……),例如:
public static void Test(int a, String... books){ for (String str : books) { System.out.println(str); } System.out.println(a); }
java中,其实也可以这样定义,如下:
public static void Test(int a, String[] books){ for (String str : books) { System.out.println(str); } System.out.println(a); }
这样也是参数可变,但是调用的时候就没那么方便,需要这样调用“Test(5, new String[]{"疯狂java讲义", "疯狂.net讲义", "Linux从入门到放弃"});”
而.net是这样的,如下,调用的时候是“Test(5, "疯狂java讲义", "疯狂.net讲义", "Linux从入门到放弃");”,而如果没有params修饰的话,调用则要“Test(5, new string[]{"疯狂java讲义", "疯狂.net讲义", "Linux从入门到放弃"});”
private static void Test(int a, params string[] strs) { foreach (var item in strs) { Console.WriteLine(item); } Console.WriteLine(a); }
而使用params参数形式的话,可以直接调用Test(),但是如果省略params的话则不行,需要手动给参数null,java也是一样。
9.关于如何使用类变量,成员变量和局部变量问题,个人非同赞同李刚老师说的,如下:
如果是属于类的共性的,就是用类变量,比如说人的眼睛个数是2,则可以把这个人类的眼睛数量设为类变量,因为人进步了,有5个眼睛,全部实例都一起变。
如果是属于类的属性的,则使用成员变量。
其他使用局部变量。
其中的规则如下(图截取自李刚老师的《疯狂java讲义》)
10.Java中,类和成员变量的默认修饰符是default,指的是包内访问权限,而.net中,默认的修饰符是internal,指的是程序集(Assembly)内访问权限;另外.net中,protect修饰符限制的是只允许该类以及该类的子类访问,而java中的protect限制的是允许包内以及其子类访问,这两大平台的限制是不一样的,两个平台的某些概念上还是有点不一样的。那么.net中是如何控制程序内访问同时其子类也可以访问呢,其实在.net中除了private,protect,internal,public这四种修饰符之外,还有一种protect internal组合修饰符,这是唯一一种组合修饰符,指的是只允许程序集内以及其子类都能访问。.net中的层和java的差不多,只是权限限制方面有点不一样,在.net中有程序集,命名空间,然后才轮到类,而java中对应有jar包,包,然后是类,大家自己可以想清楚两者的对应关系,java中没有修饰符是限制jar包内访问的。
11.java源文件的文件名必须是和其里面的public类的类名一致,如果其没有public类,则文件名可以随便起,符合命名规则就可以了,另外,一个java源文件,只允许最多一个的public的类。
12.另外,看了书,对面向对象的“封装”特性有了更全面、更深刻的认识,如下(其中,李刚老师举了个人的年龄随便设置为1000不符合现实的形象的例子,详细请看书,我就不贴上来了):
还依稀记得,以前一直想不明白,为什么c#和java都搞个get;set;访问器,不给访问就用private,不然就给protect或public不就行了么,其实是当时自己对面向对象的“封装”特性理解得不够,李刚老师这里说的就够明白,够全面了(个人非常推荐李刚老师的《疯狂java讲义》这本书,目前已经出了第三版了,里面说得真的很基础,很全面,解释得很通透易懂,当然了,比较厚,真心要点耐心,是学java的必备良药啊,点赞)。
13.JavaBean的严格定义:全部实例成员变量都是private的,而且每个实例变量都提供public的setter和getter方法,所以JavaBean总是一个封装良好的类。
14.要区分类的初始化和创建实例。当第一次使用一个类的时候,会类进行初始化,初始化的共走就是为了加载类,并且为类里面的类变量(并非是实例变量)分配内存。然后才开始创建实例,为实例变量分配内存。例如:
public Student{ public static int a; public String name; }
Student stu = new Student();
Student stu2 = new Student();
以上这语句,第一个语句的时候,显示对类进行初始化,为类变量a分配内存,然后才是新建stu的实例,第二句的时候,就不会再对类进行初始化,而是直接就创建stu2指向的实例了。这里顺便说下一个事情,一个类,只要在需要的时候才会加载进内存里面,如果你定义了个全部都是很大的类变量的类,如果整个程序运行期间都没用到这个类,这个类是不会被加载进内存里面的,就算他是类变量。
15.类的构造器,是用来初始化对象的,每个类默认都有一个无参数的构造函数。其实构造对象并非完全由构造器去构造的,是系统进入构造器体之前就已经创建了这样的一个对象,该对象这时候还不能被外界访问,只有在构造器里面用this才能范文,然后开始执行构造器里面的代码来对该对象进行初始化,然后把引用返回。其实就是说,调用构造器的时候,是分两步的:①系统根据类的结构分配内存,创建对象;②调用构造器对对象进行初始化;个人感觉成员变量并非是在默认构造函数进行默认值赋值的(int为0,bool为false,应用类型为null),而是在系统创建对象的时候就已经进行默认值初始化了。因为如下:
package 疯狂Java讲义; public class CreatorTest { public static void main(String[] args) { // TODO Auto-generated method stub Test obj = new Test("ismallboy"); System.out.println(obj.toString()); } } class Test{ public Test(String str){ this.str = str; } int i; String str; }
通过断点调试,在输出之前,obj对象的i变量就已经是0了,按道理来说,我手动建了一个带参数构造器,类就不再存在默认的无参构造器,而我并没有主动把成员变量i赋值,所以个人认为成员变量的默认值是在系统分配内存创建对象的时候就已经给了的,而不是在执行构造器的时候赋默认值的。
16.函数的重载是跟函数的返回类型以及函数的修饰符(比如:public,static,final等)无关的,只要函数名和参数类型或个数不一致就可以称之为重载(注意要和面向对象中多态中的重写区分概念)了,为什么参数类型或个数要不一样呢,因为只有这样,当调用一个方法f(int i),要根据参数个数和类型来区分调用的是哪一个方法,而不需要判断返回类型,当然了,如果int a = f()这样写的话,系统也会根据需要调用返回值为int类型的方法的。
17.同一个类的类构造器之前互相调用要用this()的方式来调用,并且必须放在构造器的第一行。
18.类的重载需要符合“两同两小一大”规则,两同指的是:方法名和形参列表相同;两小指的是,子类的返回值类型要比父类的返回值类型要小或者相等,子类抛出的异常要比父类抛出的异常要小或者相等;一大指的是子类的访问权限要比父类的访问权限大或者相等。要注意重载和重写的单词分别是:overload和overwrite,重载是同一个类里面的,而重写是两个继承的类之间的,当然重写也可以出现在两个继承类之间,如果子类方法和父类方法名一样,但是形参列表不一样的时候,也会出现重载的情况。
19.注意如果子类有和父类一样的成员变量(注意不是成员方法)的话,子类的会隐藏父类的成员变量(注意是隐藏而不是覆盖或者重写),可以用super类访问父类的成员变量,也可以通过强制将子类对象转换成父类对象来范文父类的成员变量。
20.子类调用父类构造器的话,一定要在子列构造器的第一行调用,和this调用同类的重载方法一样,都必须放在方法体的第一行。
21.数值类型包括:整数型,字符型,浮点型。
22.引用类型之间进行转换的时候,最好先用instanceof来判断是否转换,如下:
package 疯狂Java讲义; public class ConversionTest { public static void main(String[] args) { Object obj = new Integer(5); if (obj instanceof String) { String str = (String)obj; System.out.println(str); } } }
instanceof用于判断左边的对象是否可以成功转换成右边的类型,如果可以成功转换则返回true,否则返回false,这样可以强制转换的时候避免运行的时出现异常。就像以上例子,如果去掉外面包住的if,编译的时候也不会报错,只有在运行的时候才会报异常。
而在.net里面是用as或者is来判断是否可以类型转换的,如下:
Object obj = 5; if (obj is string) { Console.WriteLine((string)obj); }
另外要区别as和is,as是执行转换,如果转换成功则返回转换之后的对象,否则返回null,可以避免出现转换出现异常。而is是判断是否可以转换,可以返回true,否则返回false;is会做两次类型兼容性检查,而as只会做一次,as效率比is高。
23.继承与组合请看我的另一篇文章:继承和组合
24.初始化块分为普通初始化块和静态初始化块,并且初始化块的修改符能是static或者不加修饰符。初始化块其实是java的语法糖,编译成class文件的时候,编译器会把它编译到每个构造器的开头,所以初始化块和构造器的执行顺序是先执行初始化块(如果成员变量有赋初始值的话,则按照初始化块和定义成员变量的先后顺序执行),然后执行初始化块,一个类普通初始化块和构造器总是紧接着执行的,先执行初始化块然后是构造器,在类继承里面的执行顺序就遵循这个规则。另外静态初始化块遵循类的静态特性,在静态初始化块内部不允许访问普通的成员变量,并且类的初始化块是在类初始化的时候就执行了的,而不是每次创建对象的时候都会被执行一次,也就是说静态初始化块只是在第一次类加载的时候执行,然后这个类会一直存在于jvm里面,后面再创建该类的一个对象的时候,不会再重复执行静态初始化块,所以要对类加载的整个过程要有了解才行,后面我会单独写一篇文章来介绍类的加载到创建对象的整个过程。