javaSE语法踩坑(一)--深入理解java数据类型和运算符

基础语法

  • 一些容易忽略的语法
    • 1.标识符
      • 一般遵循以下规则
      • 以下规范
      • 小知识
    • 2.关于标识符重名
      • 方法名重载(overload) 和重写(override)
      • 需要注意
      • 隐藏(hide)
      • 注意
      • 遮蔽(shadow)
      • 遮掩(obscure)
    • 3. 关于数据类型
    • 4. 关于运算符注意
      • &用作交集类型
    • 5.switch注意点

一些容易忽略的语法

来聊一聊我在java学习中发现的一些大家都没注意到的语法点,冷门但很有趣

1.标识符

定义:凡是自己可以起名字的地方都叫标识符。
涉及到的结构:
包名、类名、接口名、变量名、方法名、常量名

一般遵循以下规则

javaSE语法踩坑(一)--深入理解java数据类型和运算符_第1张图片

以下规范

见名知意
javaSE语法踩坑(一)--深入理解java数据类型和运算符_第2张图片

小知识

Unicode基本多语言级别的字符(除运算符,分隔符以及’©’‘®’#,±等等这样的特殊字符外的char字符)都可做标识符.
也就是说大部分语言的常用文字都可以用来命名,当然也包括汉字啦.

2.关于标识符重名

方法名重载(overload) 和重写(override)

这是我们都会着重学习,使用较多的两种情况
一个实例方法可以覆写(override)在其超类中可访问到的具有相同签名的所有实例方法[JLS8.4.8.1],从而使其能动态分派(dynamic dispatch;换句话说,VM 将基于实例的运行期类型来择要调用的覆写方法[JLS 15.12.4.4]。覆写是面向对象编程技术的基础,并且是唯一没被普遍劝阻的名字重用形式.
在某个类中的方法可以重载(overload)另一个方法,只要它们具相同的名字和不同的签名。由调用所指定的重载方法是在编译期定的[JLS 8.4.9, 15.12.2].

重写方法的规则:
对于: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{ //方法体}

  •  约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
    
  • ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
  • ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符(private<缺省
  •  	>特殊情况:子类不能重写父类中声明为private权限的方法,子类方法不受限制
    
  • ③ 返回值类型:(返回值协变类型)
  •  	>父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
    
  •  	>父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
    
  •  	>父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
    
  • ④ 子类重写的方法抛出的受查异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)

需要注意

子类和父类中的同签名方法要么都声明为非static的
	同参数时即为重写,
要么都声明为static的
	注意此时与方法签名有关,且必须遵循重写的规则,也可以被@override修饰,(按语法规范不是重写,而是隐藏,见下文)。

只同名,不同签名(即不同参数)的子父类方法实际为重载,对修饰符及返回值完全没要求.

隐藏(hide)

一个域、静态方法或成员类型可以分别隐藏(hide)在其超类中可访问到的所有具有相同名字(对方法而言就是相同的方法签名)的域、静态方法或成员类型。隐藏一个成员将阻止其被继承[JLS 8.3, 8.4.8.2, 8.5]:

注意

隐藏一个父类的静态方法时需遵循与重写类似的规则.
成员类型可以指内部类和属性,不管是不是静态
阻止继承意味着,在子类中只能使用super调用父类成员,且赋值时互不影响,也就是子类对象可以有两个同名的属性可以使用,
并且因为成员类型为编译时静态绑定,出现隐藏后,用同一子类实例的不同类型的引用来操作属性等成员,会有不同的结果...应当杜绝使用
java8开始接口中有带方法体的静态方法,但不能被子接口继承,也不能被实现类继承.但是用defalut修饰的方法体正常继承....需要注意,关于接口很多的坑后续再总结

遮蔽(shadow)

一个变量、方法或类型可以分别遮蔽(shadow)在一个闭合的文本范围内的具相同名字的所有变量、方法或类型。如果一个实体被遮蔽了,那么你用它的简单名是无法引用到它的;根据实体的不同,有时你根本就无法引用到它[JLS 6.3.1];
我的理解是,当一个类及其父类中声明过一个名字的方法时(特别注意Object的那几个方法),会遮蔽掉其外部的方法,这与方法签名无关(即参数类型无关),如这个类有外部类或静态导入时,此时要引用被遮蔽的方法需加上限定词(类名/对象名 . )
最常见的当局部变量对成员变量有遮蔽时.需要用限定词this来引用成员变量,或用类名引用静态变量.
另外Java中的变量作用域嵌套,只允许上述局部变量对成员变量,不支持在其他{}的变量作用域嵌套,也就是循环体内的局部变量和代码块局部变量不能同其作用域外一级范围的局部变量同名.....与C/C++的无限嵌套不一样.......js中var只有函数作用域和全局作用域(未声明变量),ES6开始有let和const的局部作用域,支持无限嵌套.

遮掩(obscure)

一个变量可以遮掩具相同名字的一个类型,只要它们都在同一个范围内:
如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量或一个类型可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类型。如果一个类型或一个包被遮掩了,那么你不能通过其简单名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字。
遵守命名习惯就可以极大地消除产生遮掩的可能性[JLS 6.3.2, 6.5],

3. 关于数据类型

以下需要注意:

1.整型的常量,默认类型是:int型  (但这样的字面量赋值合法short s1 = 10;原因是字面量的自动类型窄化,后文详述)
2.char能表示单个Unicode字符中的基本的多语言级别,少量Unicode字符需要两个char值,
3.另外\u表示Unicode转义,特别是在注释中要小心,如表示路径!!
	小心0开头的数字会当成八进制,0b和0B当成二进制,0X和0x当成十六进制
4.关于布尔型占用内存空间:boolean(官方规范: 编译后四个字节类似int,在数组中是1个字节)
5.负值的byte,short可以强转为char,此时会发生符号扩展,完全表示不同的意思.注意不同类型的负值在二进制层面表示方法的差异
因为类型数据表示范围有限,有一些有趣的情况:
	对于整型,最小值用二进制表示为"1000000....",取负后仍为本身(short,byte需要强转),最大值加一等于最小值
	对于浮点型,因为精度问题,不能用于判等,尽量不用于比较大小
	将一个很小的浮点数加到一个很大的浮点数上时,将不会改变大的浮点数的值.毗邻的浮点数值之间的距离被称为一个ulp,它是“最小单位(unit in the lastplace”的首字母缩写词。在5.0 版中,引入了Math.ulp 方法来计算float或double 数值的ulp。

4. 关于运算符注意

1.自增自减,数据类型不变,i=i++ 中  i的值永远不变.
2.单目运算的数据类型不变
复合赋值 E1 op= E2 等价于简单赋值E1 =(T)((E1)op(E2)),其中T 是E1 的类型,除非E1 只被计算一次。
总之,不要在short、byte 或char 类型的变量之上使用复合赋值操作符。因为这样的表达式执行的是混合类型算术运算,它容易造成混乱。更糟的是,它们执行将隐式地执行会丢失信息的窄化转型,其结果是灾难性的。
赋值运算有返回值(Expressions语句),但不推荐使用. 即 X = ( Y = 表达式)  运算符 ....;
Expressions语句,有返回值,必须以";"结束.与之相对的statement语句,没有返回值,如果有}不用再加";"结束
3.逻辑运算符:& &&  |  || !  ^,,,
最后一个逻辑异或^,当且仅当两者布尔值不一样时返回true.
官方规范&& ||更准确的叫条件操作符,包括&&  ||  ?:三种.根据左边条件结果确定右边是否执行.....特别注意js不同
4.双目运算符都自动提升类型    算术和位以及比较运算
包括:比较运算符(关系运算符): == != >  <  >=  <=  instanceof
	== 和 !=: 不仅可以使用在数值类型数据之间,还可以使用在其他引用类型变量之间。
	与NaN进行比较永远返回false
位运算符:<<  >> >>> &  |  ^  ~
类型自动提升,特别是byte,short,char提升为int
	浮点数零除以零将得到NaN,浮点数对零求余得到NaN,与NaN计算永远得到NaN.	
	非零正浮点数除以零,得Infinity(正无穷大)
	非零负浮点数除以零,得-Infinity(负无穷大)  
整形进行以上操作都报错ArithmeticException算术异常
5.三目运算符的返回类型
	1.同类型运算不变
		    char x = 'x';//'x' = 120
			System.out.println(true?x:0); //x 
  	2.int类型常量表达式不会使byte,short,char转型为int(int及其以下常量窄化)
		System.out.println(true?x:0L); //120 提升为long
		System.out.println(true?x:(short)0); //120 char与short或byte运算提升为int
		int i = 0;
		System.out.println(false?i:x);//120 ,如果i有final修饰,则结果为X
	3.其他类型常量表达式或非常量表达式以大的类型为准,引用类型以最小公共父类为准(JDK5.0新增,之前要求引用类型必须具有继承关系)
6.连接符:操作符重载+:
	只能使用在String与其他数据类型变量之间使用,结果都为String。
		""+字面量
	该操作符被定义为先对它的两个操作数执行字符串转换,然后将产生的两个字符串连接到一起.对包括数组在内的对象引用的字符串转换定义如下[JLS 15.18.1.1]:
	如果引用为null,它将被转换成字符串"null"。
	否则,该转换的执行就像是不用任何参数调用该引用对象的toString() 方法一样;但是如果调用toString 方法的结果是null,那么就用字符串"null"来代替。
	7.关于负数:%结果的符号与被模数的符号相同,与模数符号无关;位运算符,要移的位数要先对被移数的位数(8/16/32/64)求余,如果是负数要在加上被移数的位数(例如int的话就是32减要移位数的绝对值).

ps:判等操作符在其两个操作数中只一个是被包装的数字类型,而另一个是原始类型时,执行的确实是数值比较。
包装类进行数值运算时会自动拆箱.
另外equal()方法会先用instanceof判断,因此两个相同数值不同类型包装类会得到false .
整数类型的包装器类(Integer、Long、Short、Byte 和Char支持通用的位处理操作,包括highestOneBit、lowestOneBit、numberOfLeadingZeros、numberOfTrailingZeros、bitCount、rotateLeft、rotateRight、reverse、signum 和reverseBytes。

&用作交集类型

JLS 4.9的语法
javaSE语法踩坑(一)--深入理解java数据类型和运算符_第3张图片

 public boolean updateTransfer(BwlTransfer transfer) {
     
        ((BwlTransfer & BaseDAO<BwlTransfer>)transfer).insert(transfer);
    }

 public static <T extends BaseEntity & Message> T xmlToObject(@NonNull Class<T> clazz, @NonNull String path);

5.switch注意点

1.switch结构中的表达式,只能是如下的6种数据类型之一:
byte 、short、char、int、枚举类型(JDK5.0新增)、String类型(JDK7.0新增)
ps:字符串的switch是通过hashCode()方法来匹配返回的int,并通过equals()来二次判断的,实质是语法糖
2.case 之后只能声明常量。不能声明范围。与javascript不同
ps:case之后没有{},可以有多条语句,声明变量的作用域在表达式后的整个{}内
3.break关键字是可选的(注意case穿透)。
4.default:相当于if-else结构中的else.
default结构是可选的,而且位置是灵活的,但不影响其最后判断的效果。
5.如果switch-case结构中的多个case的执行语句相同,则可以考虑进行合并。注意写法与js的区别
6.表达式只能为常量为null时报NPE,case常量为null时编译不了.
case类型必须能与表达式匹配否则报错.
涉及到final和自动窄化…
7.switch在java13迎来增强
1. 简化fall-through规则
->箭头操作符
2. switch作为表达式(expression)使用
break返回值,最新的java13引入了yield关键字来代替

你可能感兴趣的:(java,javase,编程语言,面向对象编程)