Java语法知识 --整理自《thingking in Java》

来源:互联网 发布:灰色裤子怎么搭配知乎 编辑:程序博客网 时间:2024/06/10 04:12

Java语法知识 –整理自《thingking in Java》


本文没有整理集合框架以及多线程的相关知识.集合框架以及多线程将会专门开出多篇源码阅读笔记类博文.

  1. String s; 创建了一个引用,而不是一个对象.如果此时给对象发消息就会返回一个运行时错误.
  2. String s = “asdf”;这里用到了一个特性 : 字符串可以用带引号的文本初始化.
  3. 基本类型
基本类型 大小 char 16bit byte 8bit short 16bit int 32bit long 64bit float 32bit double 64bit boolean - void -

所有类型都有正负号,所以不要去寻找无符号的数据类型
4. 在作用域内的变量只能用于作用域结束前.
尽管下面的代码在C和C++中是合法的,但是Java却不能这样书写.

{    int x = 12;    {        int x = 96;//Java不允许作用域覆盖    }}

5.基本成员默认值

基本类型 默认值 boolean false char ‘\u0000’(null) byte (byte)0 short (short)0 int 0 long 0L float 0.0f double 0.0d

6. 方法名和参数列表(它们合起来被称为”方法签名”)唯一的标识出某个方法.
7. 如果试图在某个对象上调用它不具备的方法,那么在编译时就会得到一条错误消息.
8. static的方法使用对象和类名都可以调用.当使用对象调用时,看左边的进行分派.
9. java.lang是默认导入到每个java文件的.
10. 所有的javadoc命令都只能在\**内出现.和通常一样,注释结束于*/
11. 几乎所有的操作符都只能操作基本类型.例外的操作符是=,==!=,这些操作符能操作所有的对象.除此之外String类支持++=.
12. 最简单的优先级规则就是先乘除再加减.程序员经常会忘记其他优先级规则,所以应该用括号明确规定计算顺序.
13. 对对象”赋值”的时候,我们真正操作的是对对象的引用.所以倘若”将一个对象赋值给另一个对象”,实际是将”引用”从一个地方复制到另一个地方.这意味着假若对对象使用c=d,那么c和d都将指向原本只有d指向的对象.
14. 一元减号用于转变数据的符号,而一元加号只是为了与一元减号对应,作用仅仅是将较小类型的操作数提升为int.
15. 对于++a和–a,会先执行运算再生成值.而对于a–和a++会先生成值再执行运算.
16. 直接常量后面的后缀字符标识了它的类型.若为大写(或小写)的L,代表long.大写(或小写)的字母F,代表float,大写(或小写)的字母D,则代表double.
17. 如果试图将一个变量初始化成超出自身表示范围的值,编译器都会向我们报告这一条错误信息.
18. boolean类型可以对它执行按位”与”,按位”或”和按位”异或”运算,但是不可以执行按位”非”操作.按位操作符和逻辑操作符有一样的效果,只是他们不会中途”短路”
19. 移位操作符只可以用来操作整数类型.
20. 左移操作符在低位补0,右移在高位补符号位.Java中增加了”无符号”右移操作符>>>,无论正负都在高位补0.
21. 移位运算符只是计算出一个值,并不会改变原来变量的值,要想改变原来变量的值,还需要再赋值回去.
22. “移位”可与”等号”(<<=或>>=或>>>=)组合使用.此时,操作符左边的值会移动由右边的值指定的位数,再讲得到的结果赋给左边的变量.
23. 如果对char,byte或者short类型的数值进行移位处理,那么在移位之前,它们会被转成int型变量,并且得到的结果也是一个int值.只有数值的右5位才有用,防止移位超过int的位数.
24. 如果对byte或short值进行这样的移位运算,得到的可能不是正确结果.他们会先被转换成int类型,再进行右移操作,然后被拦截,赋值给原来的类型,在这种情况下可能会得到-1的结果.下面这个例子演示了这种情况.

public class URShift {  public static void main(String[] args) {    int i = -1;    print(Integer.toBinaryString(i));    i >>>= 10;    print(Integer.toBinaryString(i));    long l = -1;    print(Long.toBinaryString(l));    l >>>= 10;    print(Long.toBinaryString(l));    short s = -1;    print(Integer.toBinaryString(s));    s >>>= 10;    print(Integer.toBinaryString(s));    byte b = -1;    print(Integer.toBinaryString(b));    b >>>= 10;    print(Integer.toBinaryString(b));    b = -1;    print(Integer.toBinaryString(b));    print(Integer.toBinaryString(b>>>10));  }} /* Output:111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111*/

25.如果表达式以一个字符串起头,那么后续所有操作数都必须是字符串型.

int x = 0,y = 1,z = 2;String s = " " + x + y + z;//s 的值为012

26.Java编译器不会把int值转成boolean值.

while(x = y)

27.Java允许我们把任何基本数据类型强转(或自动向上转型)成别的数据类型,但是boolean类型除外,boolean转其他和其他转boolean都是禁止的.
28. 对float和double转成整形值的时候,总是对数字进行截尾.如果想要四舍五入的话可以使用java.lang.Math中的round()方法.
29. java中没有sizeof(),java中所有数据类型在任何机器中都是相同的.
30. 对boolean类型进行的运算非常有限.我们只能赋值为true和false值,并测试它是真还是假,而不能相加,或对boolean值进行其他任何运算.
31. 如果对两个足够大的int值执行乘法运算,结果就会溢出.但是编译器和运行时都不会异常.
32. Java中唯一用到逗号操作符的地方就是for循环控制表达式.
33. 在一个控制表达式定义多个变量的能力只限于for循环.
34. java中也可以使用标签.标签是后面跟有冒号的标识符,就像这样label1:,Java中,标签唯一起作用的地方刚好是在迭代语句之前.

label:outer-iteration{    inner-interation{        //...        break;        //...        continue;        //...        continue label;        //...        break label1;    }}

35.C语言中没有重载
36. 每个重载的方法都必须有一个独一无二的参数类型列表,甚至参数顺序不同也足以区分两个方法,但是访问权限修饰符和返回值不会影响.
37. 编译器会为每个类生成一个默认构造器.但是如果已经定义了一个构造器(无论是否有参数),编译器就不会帮你自动创建默认构造器.
38. 在构造器中调用其他构造器可以使用this(param)
39. 尽管可以使用this调用构造器,但是只能调用一次,而且必须放在最开始的地方.
40. 在类的内部,成员变量定义的先后顺序决定了初始化的顺序.即使变量定义散布在方法定义之间,他们仍旧会在任何方法(包括构造器)被调用之间得到初始化.
41. Java定义数组的两种方式

int[] a1;//推荐使用这一种方法.int a1[];

42.Java不允许局部变量不经过初始化就使用,在编译时就会报错.
43. int[] a1 = { 1, 2, 3, 4, 5};这种方式定义数组编译器会分配存储空间.
44. 使用可变参数列表时,当你指定参数时,编译器实际上会为你去填充数组.如果直接传递一个数组进去,那么这个数组就直接是整个可变参数列表.
45. enum可以在switch中使用.
46. 包内包含有一组类,它们在单一的名字空间之下被组织在了一起.
47. 在同一个目录下但是没有给自己设定任何包名的java文件,Java将这样的文件自动看作是隶属于该目录的默认包之中.
48. 当编写一个Java源代码文件时,此文件通常被称为编译单元.每个编译单元都必须有一个后缀名.java,而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同.每个编译单元只能有一个public类,否则编译器就不会接受.如果编译单元内还有其他类,那么在包外面是看不见的,因为他们不是public类,而且他们主要是用来 为主public类提供支持.
49. 如果使用package语句,它必须是文件中除注释以外的第一句程序代码.在文件起始处写package access;
50. Java解释器运行过程如下:

首先,找出环境变量CLASSPATH,CLASSPATH包含一个或多个目录,用作查找.class文件的根目录.从根目录开始,解释器获取包的名称并将每个句点替换成反斜杠,以从CLASSPATH根中产生一个路径名称.得到的路径会与CLASSPATH中各个项相连,解释器就在这些目录中查找与你所要创建的类名称相关的.class文件.(解释器还会去查找某些涉及java解释器所在位置的标准目录)

51.Java访问权限分为

  • public : 接口访问权限,对每个人都是可用的.
  • protected : 继承访问权限,也提供包访问权限
  • 没有修饰符 : 包内访问权限
  • private : 私有权限,只有自己内部才能访问

52.尽管不太常用,但是一个编译单元内完全不带public也是可能的.
53. 类不可以是private的也不可以是protected的.对于类的权限,只能是包访问权限或public.如果不想外界访问可以把构造器都设置为private.
54. 事实上,一个内部类可以是private或者是protected的,但那是一个特例.
55. 当继承一个基类时,会自动得到基类中所有的域和方法.
56. 即使是一个程序含有多个类,也只有命令行所调用的那个类的main()方法会被调用.即使一个类只具有包访问权限,其public main()仍然是可访问的.
57. 如果没有默认的基类构造器,或者想调用一个带参数的基类构造器,就必须用关键字super显式地编写调用基类构造器的语句,并且配以适当的参数列表.
58. 调用基类构造器是你在导出类构造器中要做的第一件事
59. @override注解可以防止你在不想重载时而以外的进行了重载.
60. 到底用组合还是集成一个最清晰的判断方法就是问问自己是否需要从新类向基类进行向上转型.
61. 组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形.
62. 一个既是static又是final的域只占据一段不能改变的存储空间.
63. 对于基本类型final使数值保持不变,而对于对象引用,final使引用恒定不变.
64. Java允许生成”空白final”,所谓空白final是指被声明为final但是没有赋初值的域.无论什么情况,**编译器都保证空白final在使用前必须被初始化”.
65. Java允许在参数列表中以声明的方式将参数指明为final.这意味着你无法在方法中更改参数所指向的对象.
66. final方法无法被覆盖.
67. 类中所有的private方法都隐式的指定为final的.
68.

子类中有一个和父类中的private权限相同的方法,这种方法的名称一直不叫做方法的覆盖
方法的覆盖指的是子类可以用父类的一个方法,但是子类想建立自己的方法,于是子类写了一个一模一样的,把父类盖上.
但是如果,子类不知道父类的某些方法,那么覆盖就无从谈起.
语义上在当前类对private成员的访问不会受到继承的影响.
当子类覆盖父类的成员变量时,父类方法使用的是父类的成员变量,子类方法使用的是子类的成员变量

public class A{    public int m = 1;    private void pri(){        System.out.println("A : m"+ m);    }    public void pub(){        this.pri();    }    public static void main(String[] args){        B b = new B();        b.pub();    }}class B extends A {    public int m = 2;    public void pri(){        System.out.println("B : m"+ m);    }}输出 A : m = 1;因为根本没有发生覆盖,方法pub在A的语义中是调用A的pri所以最后执行的时候调用的是A的.如果把类A的pri()函数的权限改成public那么执行结果为B : m 2 ;这是因为当A的pri为public的时候,B中的pri函数就发生了覆盖,这时候pub中的结果就是覆盖后的结果(构造器中发生多态).

69.final类无法被继承
70. 初始化的顺序.
71. 多态的作用是消除类型之间的耦合关系
72. 只有非private方法才可以被覆盖,但是还是要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行.确切的说在导出类中,对于基类中的private方法最后采用不同的名字.
73. 如果某个方法是静态的,它的行为就不具有多态性.
74. 对于一个没有父类(或者说除了object之外没有父类)的类来说,对象的一次初始化过程如下:

  1. 递归的找到父类
  2. 首先按照定义的顺序,从上到下初始化静态成员(如果是第一次使用该类的话);
  3. 按照定义的顺序,从上到下执行静态代码块(可以有多个静态代码块)
  4. 分配存储空间(并清零)
  5. 从上到下初始化成员属性
  6. 执行构造代码块

75.对于一个有父类的类(递归过程)的对象初始化过程是这样的:

  1. 首先从上到下初始化父类的类中静态成员
  2. 从上到下执行父类中静态代码块
  3. 给对象分配存储空间,并初始化为0
  4. 从上到下初始化父类成员变量
  5. 从上到下执行父类构造代码块
  6. 执行父类的构造函数(默认或者显式执行有参数)
  7. 对自身类执行上述1-4步

76.并不是编译器生成的才是默认构造器,显式写出来的无参数构造器也是默认构造器,也会被子类构造器默认调用.
77. 构造器中发生了多态,如果子类覆盖了父类方法,且父类构造器中调用了该方法,会发生多态现象,最终调用子类的该方法.但是此时子类的初始化还没进行,可能导致意想不到的结果.所以尽量不要在构造器中调用其他方法,或者仅仅调用final以及private(private默认final)的方法;
78. 接口也可以包含域,不过这些域隐式的是static和final的.
79. 接口中的方法默认是public的(即使没有进行声明),当要实现一个接口时,在接口中定义的方法必须是public的.
80. 接口中定义的域不能是”空final”但是可以被非常量表达式初始化.
81. 接口支持多继承.普通类支持多实现.
82. 多重继承时,会因为重载实现和覆盖导致编译错误.

interface I1{void f();}interface I3{int f();}class C { public int f() {return 1;}}//下面两行编译错误//class C1 extends C implements I1{}//interface I4 extends I1,I3 {}

83.在外部类的其他方法中访问内部类直接写名字.在非外部类的类的方法中访问内部类需要写OuterClassName.InnerClassName.
84. 内部类对象能无条件访问其外围对象的所有成员.
85. 当某个外部类对象创建了一个内部类对象时,内部类对象会秘密的捕获一个指向外围类对象的引用.当你访问外围类的时候,就是用这个引用来选择外围类的成员.
86. 当需要生成对外部类对象的引用的时候,可以用外部类的名字后面紧跟.this来实现.这样产生的引用自动具有正确的类型.
87. 用某个外部类对象去创建它的某个内部里对象时,可以用外部类对象的引用后面跟上.new语法.
88. 在拥有外部类对象之前是不可能创建内部类对象的.这是因为内部类对象会暗暗连接到创建它的外部类对象上.但是如果使用嵌套类(静态内部类),那么就不需要对外部对象的引用.
89. 在任意作用域内都可以创建一个内部类.这时这个内部类是作用域的一部分,而不是外部类的一部分.但是并意味着作用域一结束,内部类就不能用了.
90. new匿名内部类的语句返回的引用会自动向上转型为父类(或者接口)的引用.
91. 在匿名类中定义字段时,还能够对其执行初始化操作.
92. 如果定义一个匿名内部类,并且希望它使用一个其外部定义的对象,那么编译器会要求其参数引用是final的.
93. 匿名内部类没有构造函数(因为它没有名字),但是可以用构造代码块来为它进行初始化.
94. 匿名内部类和正规的继承相比有些受限,因为匿名内部类既可以拓展类,也可以实现接口,但是不能两个兼备.而且如果实现接口也只能实现一个接口.
95. 如果不需要内部类对象和外部类对象有联系,可以将内部类声明为static.这通常称为嵌套类.
96. 要创建嵌套类的对象,并不需要外部类的对象,但是需要用外部类的类名去引用内部类的类名(因为内部类的类名是隐藏在外部类的空间中的)
97. 不能从嵌套类的对象中访问非静态的外部类对象.
98. 嵌套类可以作为接口的一部分.但是放到接口的任何类都是自动public和staic的,因为类是static的,所以只是把嵌套类置于接口的命名空间内.
99. 一个内部类被嵌套多少层并不重要,它能透明的访问所有它嵌入的外部类的所有成员.
100. 内部类和外部类具有相互可见性.
101. 当继承一个内部类时,需要再构造方法中传入一个外部类的引用,并调用外部类引用的super()方法.这样才能提供必要的引用,程序才能编译通过.
102. 覆盖一个内部类并不会产生什么作用,他们各自在各自的命名空间内,在哪个空间内引用,那么引用的就是哪个空间的内部类.

//: innerclasses/BigEgg.java// An inner class cannot be overriden like a method.import static net.mindview.util.Print.*;class Egg {  private Yolk y;  protected class Yolk {    public Yolk() { print("Egg.Yolk()"); }  }  public Egg() {    print("New Egg()");    y = new Yolk();  }}   public class BigEgg extends Egg {  public class Yolk {    public Yolk() { print("BigEgg.Yolk()"); }  }  public static void main(String[] args) {    new BigEgg();  }} /* Output:New Egg()Egg.Yolk()*///:~

明确继承某个内部类也是可以的

//: innerclasses/BigEgg2.java// Proper inheritance of an inner class.import static net.mindview.util.Print.*;class Egg2 {  protected class Yolk {    public Yolk() { print("Egg2.Yolk()"); }    public void f() { print("Egg2.Yolk.f()");}  }  private Yolk y = new Yolk();  public Egg2() { print("New Egg2()"); }  public void insertYolk(Yolk yy) { y = yy; }  public void g() { y.f(); }}public class BigEgg2 extends Egg2 {  public class Yolk extends Egg2.Yolk {    public Yolk() { print("BigEgg2.Yolk()"); }    public void f() { print("BigEgg2.Yolk.f()"); }  }  public BigEgg2() { insertYolk(new Yolk()); }  //这一句的new yolk()会调用父类和子类的构造,这是用显式继承父类内部类yolk()实现的.  public static void main(String[] args) {    Egg2 e2 = new BigEgg2();    e2.g();  }} /* Output:Egg2.Yolk()New Egg2()Egg2.Yolk()BigEgg2.Yolk()BigEgg2.Yolk.f()*///:~

103.使用局部内部类而不使用匿名内部类的唯一理由是:我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化.
104. 内部类命名规则:外部类的名字加上$,再加上内部类的名字.如果是匿名内部类,编译器会简单生成一个数字作为其标识.
105. 能够抛出任意类型的Throwable对象,它是异常类型的根类.通常对于不同类型的异常要抛出相应的异常.
106. 异常处理机制只会找第一个对应的catch子句,找完就结束了,不会继续往下执行.所以要求父类异常的catch应该在子类异常的catch下面,否则会报编译错误.
107. 要定义自定义异常,必须从已有的异常中继承.
108. System.err不会随着System.out被重定向.
109. 如果方法里面throws了异常,但是却没有处理.编译器会提醒你要么捕获,要么throws.否则编译错误
110. 无论异常是否发生,finally子句一定会被执行.
111. 通常不用捕获RuntimeException,但还是可以在代码中抛出RuntimeException异常.
112. 对于RuntimeException编译器不需要异常说明,其输出被报告给了System.err.
113. 甚至在当前catch没有catch住异常的情况下,finally也会执行.
114. 如果在finally中返回,那么即使抛出了异常也不会产生任何输出.
115. 当覆盖方法的时候,只能抛出基类方法的异常说明里列出的那些异常.
116. 异常限制对构造器不起作用,构造器可以抛出任意类型的异常而不用管基类构造器中抛出的异常.
117. 派生类方法可以不抛出任何异常,即使它是基类所定义的异常.但是如果将它向上转型成基类,那么编译器就会要求你捕获基类的异常
118. 异常说明本身并不属于方法类型的一部分.在继承中某个特定方法的”异常说明”变小而不是变大.
119. String类是final的.
120. String对象是不可变的.String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串的内容.
121. 用于String的++=是Java中仅有的两个重载过的操作符,而Java并不允许程序员重载任何操作符.
122. 当使用String时,编译器自动引入了StringBuilder类,并创建了一个StringBuilder对象.用以构造最终的String.并为每个字符串调用一次append()方法.总计四次,最后调用toString()生成结果.
123. 如果在循环中使用String的+号,而不是StringBuilder的话,那么每循环一次,都会创建一个StringBuilder对象.所以如果你要在toString()方法中使用循环,那么最好自己创建一个StringBuilder对象,用它来构造最终的结果.
124. StringBuilder是Java5引入的,之前使用的是StringBuffer,StringBuffer是线程安全的,因此开销更大.
125. 一个常见的由于toString()引起的递归错误是这样的:

public String toString(){    return "InfiniteRecursion address: " + this + "\n";}

126.Java中所有的类型转换都是在运行时进行正确性检查的.
127. Class对象就是用来创建类的所有的”常规”对象的.Java使用Class对象来执行RTTI,即使你正在进行的是类型转换这样的操作.
128. 类加载器首先检查这个类的Class对象是否已经被加载.如果尚未加载,默认的类加载器就会根据类名查找.class文件.
129. forName()就是取得Class对象引用的一种方法.他是用一个String对象(必须使用包含包名的全限定名)作为输入参数,返回一个Class对象的引用.如果还没有加载就加载它并执行static成员.
130. getName()可以得到全限定的类名,并分别使用getSimpleName()getCanonicalName()(Java5)来获取简单名和全限定名.
131. 使用.class来创建对Class对象的引用时,不会自动初始化该Class对象.初始化被延迟到了对静态方法或者非常数静态域或者构造方法进行首次引用时才执行.
132. 如果一个static final值是”编译期常量”,那么这个值不需要初始化类就可以被读取.如果一个static final的对象的值编译期无法获取,那么它就不是编译期常量.
133. 泛型用在Class对象的时候可以限定Class引用对象类型.
134. instanceof只可将其与命名类型进行比较,而不能与Class对象进行比较.
135. 通过反射,可以调用类的任何方法,包括private方法,并且可以访问private域.对final域的修改不会报错,但是实际上不会发生.
136. 基本数据类型无法作为类型参数,但是Java5具备了自动打包的功能.
137. 是否拥有泛型方法与其所在的类是否是泛型没有关系.
138. 定义泛型方法,把类型参数放在返回值前面.
139. 使用泛型类时,必须在创建对象时指定具体类型参数的值,使用泛型方法的时候,通常不必指明泛型参数,因为编译器会为我们找出具体类型,这称为类型参数推断.
140. 如果调用方法时传入基本类型,自动打包机制就会介入其中.
141. 在泛型之前,其他容器处理对象时,都将他们视作没有任何具体类型.数组之所以优于泛型之前的容器,就是因为你可以创建一个数组去持有某种具体类型.
142. 无论使用哪种类型的引用,数组标识符其实是一个引用,指向在堆中创建的一耳光真实对象,这个对象用于保存其他对象的引用.
143. .length是获取数组长度的唯一方法..length()是获取字符串的方法.
144. []语法是访问数组元素的唯一方法.
145. 基本类型数组如果是数值型的,就会被自动初始化为0,如果是字符型,就会被初始化为(char)0,如果是boolean型的,就会被初始化为false;
146. 数组与泛型不能很好的结合.你不能初始化具有参数化类型的数组.

Peel<Banana>[] peels = new Peel<Banana>[10];//非法
0 0
原创粉丝点击