第三章 数据类型和运算符
Java语言是强类型语言,意思是每个变量和每个表达式都有一个在编译时就确定的类型,所有的变量都必须显式声明类型
标识符就是类,变量、方法命名的符号
标识符不能包含空格
标识符只能包含美元符($),不能包含其他特殊字符
java关键字都是小写
基本数据类型:(单位:字节)
1 - byte
2 - short
4 - int
8 - long
2 - cchar
4- flaot
8-double
1-boolean
如果使用一个巨大的整数常量(超出int类型的范围),java不会自动把这个整数常量当成long类型来处理
字符集
什么时字符集?
严格来说,计算机无法保存电影、音乐、字符,计算机只能保存二级制码,因此这些东西要先转换成二进制码才能保存 ,所以才会出现各种各样的格式——mp3,wna;之所以需要这些格式,是用来将这些文件转换为二进制码才能保存。对于保存字符就简单多了,直接把所有需要保存的字符编号,当计算机要保存某个字符时,只要将该字符的编号转换为二进制码,然后保存起来。所谓字符集,就是给所有字符的编号组成组合。早期美国人给英文字符、数字、标点符号等字符进行了编号,让们认为所有字符顶多100多个,这就是ASCII字符集,后来,其他国家的加入,美国人又为这些语言的字符进行统一编号,,这次他们用了2字节(16位,支持65536个字符编号),这就是Unicode字符集。
字符型值的三种表示形式: //直接通过单个字符来指定字符型值 char c = 'A'; //通过转义字符表示特殊字符型值 char a = '\n'; //直接使用Unicode值来表示字符型值,格式时'\uXXXX',其中XXXX代表一个十六进制的整数 public class CharTest{ public static void main(String[] args){ char c = 97; System.out.println(c); //会直接打印字母a,这个是在ASCII字符集中的 } }
如果把0-65535范围内的一个int整数赋给char类型,系统会自动把这个int整数当作char类型来处理
还是记忆一下转义字符:
\b ->退格符
\n ->换行符
\r->回车符
制表符的功能是在不使用表格的情况下在垂直方向按列对齐文本
\t是制表符,如果前面输出的内容是8的倍数,则\t将输出8个空格;如果不是,则补齐为8的倍数。
\t->制表符
Java7引入的新功能:程序员可以在数值中使用下划线,不管是整数数值,还是浮点型数值
//字符串"true"和"false"不会直接转换成boolean类型,但如果直接使用一个boolean类型的值和字符串进行连接运算,则boolean类型的值会自动转换成字符串 String str = true + ""; System.out.println(str);
3.4.6 使用var定义变量
使用var定义局部变量时,必须在定义局部变量的同时指定初始值,否则编译器无法推断该变量的类型
var a = 20; System.out.println(a); var b=3.4; System.out.println(b); var c = (byte)13; System.out.println(c);
REMEMBER ME
day 3 10.12
float a = (float)5.6 //如果没有float是不行的,因为double的字节比float的字节大 //5.6系统默认是double
在一般情况下,字符串不能转换为基本类型,但通过基本类型对应的包装类可以实现把字符串转换为基本类型
String a ="45"; int iValue = Integer.parseInt(a);
3.5.3 表达式类型的自动提升
当一个算数表达式包含了多个基本类型的值,整个算数表达式的数据类型将发生自动提升。
Java定义了一些规则:
1.所有的byte类型、short类型和char类型都将被提升到int类型
2.等号右边的类型必须小于左边的类型
如果表达式中包含了字符串,这个加号时一个字符串连接运算符,而不是进行加法运算
System.out.println('a'+7+"hello"); //我们来分析一下 'a'+7会转化为int,那么就是97+7 = 104 //104+"hello"就变成字符串的连接运算
3.6直接量
直接量就是通过源代码直接给出的值,int a = 5;
3.6.1直接量的类型
能指定直接量的通常只有三种类型:基本类型,字符串类型和null类型。
识记内容:
0b/0B是二进制开头的
➢ long类型的直接量:在整型数值后添加l或L后就变成了long类型的直接量。例如3L、0x12L(对应十进制的18L)。 ➢ float类型的直接量:在一个浮点数后添加f或F就变成了float类型的直接量,这个浮点数可以是标准小数形式,也可以是科学计数法形式。例如5.34F、3.14E5f。 ➢ double类型的直接量:直接给出一个标准小数形式或者科学计数法形式的浮点数就是double类型的直接量。例如5.34、3.14E5。 ➢ boolean类型的直接量:这个类型的直接量只有true和false。 ➢ char类型的直接量:char类型的直接量有三种形式,分别是用单引号括起来的字符、转义字符和Unicode值表示的字符。例如'a'、'\n'和'\u0061'。 ➢ String类型的直接量:一个用双引号括起来的字符序列就是String类型的直接量。 ➢ null类型的直接量:这个类型的直接量只有一个值,即null。
关于字符串直接量,当程序第一次使用某个字符串直接量时,Java会使用常量池来缓存该字符串直接两,如果程序后面的部分需要用到该字符串直接量的时候,Java会直接使用常量池中的字符串直接量
常量池
常量池指的是在编译期被确定,并被保存在已编译到达.class文件中的一些数据。它包括关于类、方法、接口中的常量,也包括字符串直接两
String a = "hello"; String b = "hello"; String c = ""
10.14
3.7 运算符
/:除法运算符,如果两个操作数中有一个是浮点数,或者两个都是浮点数,这个结果就是自然除法的结果。而且此时除法是0,或者0.0,得到的结果就是正无穷大或负无穷大
public class DivTest{ public static void main(String[] args){ double a = 5.2; double b = 3.1; double div = a/b; //div的值将是1.6774193548387097 System.out.println(div); //输出正无穷大:Infinity System.out.println("5除以0.0的结果是:" + 5 / 0.0); //输出负无穷大:-Infinity System.out.println("-5除以0.0的结果是:" + - 5 / 0.0); //下面代码将出现异常 //java.lang.ArithmeticException: / by zero System.out.println("-5除以0的结果是::" + -5 / 0); } }
Infinity:无限
%:
同样的道理,如果求余运算中两个操作数中有一个或两个是浮点数,则允许第二个操作数是0/0.0
求余结果是非数:NaN
public class ModTest { public static void main(String[] args) { double a=5.2; double b=3.1; double mod=a % b; //mod的值为2.1 System.out.println(mod); //输出非数:NaN System.out.println("5对0.0求余的结果是:" + 5 % 0.0); //输出非数:NaN System.out.println("-5.0对0求余的结果是:" + -5.0 % 0); //输出0 System.out.println("0对5.0求余的结果是:" + 0 % 5.0); //输出非数:NaN System.out.println("0对0.0求余的结果是:" + 0 % 0.0); //下面代码将出现异常 //java.lang.ArithmeticException: / by zero System.out.println("-5对0求余的结果是:" + -5 % 0); } }
++:自加
注意下面这段代码
int a=5; //让a先执行算术运算,然后自加 int b=a++ + 6; //输出a的值为6,b的值为11 //首先执行算数表达式,5+6=11 System.out.println(a + "\n" + b);
3.7.3 位运算符
&:按位与
I:按位或
~:按位非
^:按位异或
当两个操作数不同时才返回true,如果两个操作数相同则返回false。
:无符号右移运算符
感觉唯一需要注意的就是
0^0 = 0
1^1 = 0
比较运算符: ==:如果进行比较的是两个数值类型,即使他们的数据类型不相同,只要值相同,返回true
97 = 'a'; 5.0 = 5; //这两个都是true ture == false; //返回false,这个是允许的
但如果两个是引用类型,那么只有当这两个引用变量引用相同类的实例时才可以比较,而且这两个引用必须指向同一个对象才可以返回true
使用javadoc来编写一个api程序
/** * @author xzc * @version jdk1.8.0 */ public class apiTest{ /** * 求输入两个参数范围以内整数的和 * @param n 接收的第一个参数 * @param m 接收的第二个参数 * @return 两个参数范围以内整数的和 */ public int add(int n,int m){ int sum = 0; for(int i= 0;i<=m;i++){ sum = sum * i; } return sum; } }
使用单行、多行和文档注释,说出他们分别适用于哪里。完整的使用出javadoc的所有@属性。 单行:注释短的注释,没什么要求 多行:注释长的注释,没什么要求 文档注释:分为3种 (1)类注释:要求放在类的前面,常用@author、@version (2)方法注释:要求放在方法前面,常用@param、@return (3)变量注释:要求放在成员变量前面 , 并不会在文档中显示,不知道为什么 5、在java文档注释里使用html5的规范。还挺好用
6、javadoc对类文件生成文档,看看public以外的外部类会不会生成文档。不会,只有public类才会 ,所以要把java文件分开写,一个类一个文件,再测试一下生成包文档,只要放在一个文件下,就只会生成public类的文档
7、说出标识符的命名规则 第一,要能够看懂 第二,不要乱用特殊符号 第三,尽量简洁 具体规则: 不能由数字开头 不能是java的关键字 不能包含空格 只能包含$和_这两种特殊符号和字母、数字,其他都不行
8、说出java的基本数据类型有哪些,以及他们分别是多少字节
char 2字节 byte 1字节 short 2字节 int 4字节 long 8字节 float 4字节 double 8字节
switch语句
使用switch语句时,有两个值得注意的地方:
第一个地方是switch语句后的expression表达式的数据类型只能是byte、short、char、int四个整数类型和枚举类型;String属于引用类型
第二个地方是如果省略了case后代码块的break;将引入一个陷阱
10.15
4.5.1定义数组
int[]是一种数据类型,一样可以使用该类型来定义变量,也可以使用该类型进行类型转换等
所以我们呢也推荐这样一种写法格式
type[] arrayName; type arrayName[];//我们一般先写变量类型,再写变量名
4.5.3 数组的初始化
所谓初始化,就是为数组的数组元素分配空间,并为每个数组元素赋值
能不能只分配内存空间,不赋值?
不行,一旦为数组的每个数组元素分配了内存空间,每个内存空间里存储的内容就是该数组元素的值,即使这个内存 空间存储的内容是空(null)。
其实它在分配内存空间的时候,就已经赋值了,这个是系统的动态初始化
null也是初始值,
只要为数组元素分配了内存空间,数组元素就具有初始值。
初始值的获得由两种形式:一种由系统自动分配,另一种由程序员指定
数组初始化的两种方式:
静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组长度
//eg: int[] a ={5,6,7,8};//不需要new int
动态初始化:初始化时程序员只指定数组长度(其实就是指定内存),由系统为数组元素分配初始值
Object[] book = new String[4];
➢ 数组元素的类型是基本类型中的整数类型(byte、short、int和long),则数组元素的值是0。 ➢ 数组元素的类型是基本类型中的浮点类型(float、double),则数组元素的值是0.0。 ➢ 数组元素的类型是基本类型中的字符类型(char),则数组元素的值是'\u0000'。 ➢ 数组元素的类型是基本类型中的布尔类型(boolean),则数组元素的值是false。 ➢ 数组元素的类型是引用类型(类、接口和数组),则数组元素的值是null
var a1 = new String[] {"xiaoming","daxioong"}; //要指定相关初始值 var a2 = {5,6,7,8};//error var无法知道类型
如果访问数组元素时指定的索引值小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时出现异常:java.lang.ArrayIndexOutOfBoundsException:N(数组索引越界异常),异常信息后的N就是程序员试图访问的数组索引。
4.6.1 内存中的数组
识记的数组对象被存储在堆内存中,如果引用该数组对象的数组引用变量时一个局部变量,那么它被存储在栈内存中
public class ArrayInRam{ public static void main(String[] args){ //identify and initialize the array,static initial int[] a= [5,7,20]; //var 动态初始化 var b = new int [4]; for(var i:b){ System.out.pritln(i); } //因为两者都是int类型数组,可以使b指针指向a指针 System.out.println("b数组的长度:"+b.length); System.out.println("a数组的长度:"+a.length); b =a ; System.out.println("b指向a数组后的长度:"+b.length); } }
4.6.3
引用数组的初始化
引用类型数组的数组元素是引用
既可以直接作为值(基本数据类型),也可以作为引用(引用类型),指向其他具体值
class Person { public int age; public double height; //info 是信息的意思 pubic void info() { System.out.println("My age is "+age + "My height is "+ + height); } } public class ReferenceArrayTest { public static void main(String[] args){ //initialize and identify Person[] students = new Person[2]; //invite two example var zhang = new Person(); zhang.age = 18; zhang.height = 180; var xu = new Person(); xu.age = 18; xu.height = 172; students[0] = zhang; students[1] = xu; //下面两行代码结果一样,因为指向的是相同的 xu.info(); students[1].info(); } }
4.6.4 没有多维数组
type[][] arrname = new type[length][]; //相当于定义了length个type[]类型的变量
public class TwoDimensionTest { public static void main(String[] args){ //定义一个二维数组 int[][] a; //将a当成一维数组来处理 a = new int[4][]; for(int i = 0,len = a.length;i<len;i++) { System.out.println(a[i]); } //initialize a 数组的第一个元素 a[0] = new int[2]; a[0][1] = 6; //a数组的第一个元素是一个一维数组 for(int i = 0,len=a[0].length;i<len;i++){ System.out.println(a[0][i]); } } }
这里最重要的是将第一个元素当成一个数组
//use static initialization to initialize String[][] str1 = new String[][]{new String[3],new String[]{"hello"}}; //使用简化静态初始化方法 String[][] str2 = {new String[3],new String[]{"hello"}};
二维数组是一维数组,其数组元素是一维数组;三维数组也是一维数组,其数组元素是二维数组……从这个角度来看,Java语言里没有多维数组。
4.6.5 操作数组的工具类:Arrays
因为Arrays中有大量的static方法,可以直接使用
注意点:
使用二分查找需要保证数组的大小是从小到大
equals方法只是保证数组之间的长度和元素相同,不是保证地址
10.16 第五章 面向对象 (上)
5.1.1 定义类
{修饰符} class 类名 { 零到多个构造器定义…… 0到……成员变量 0到……方法 }
定义成员变量的语法格式:
{修饰符} 类型 成员变量名 [=默认值]
修饰符:可以省略,也可以是public,protected,private,static,final,其中public ,protected,private三个最多只能出现其中一个,可以与static,final组合起来修饰成员变量
类型:基本类型和引用类型,只要java允许
成员变量名:骆驼命名法
定义方法语法格式:
[修饰符] 方法返回值类型 方法名(形参列表) { //零到多条执行语句组成的方法体 }
修饰符:
修饰符可以省略,也可以是public、protected、private、static、final、abstract,其中public、protected、private三个最多只能出现其中之一;abstract和final最多只能出现其中之一,它们可以与static组合起来修饰方法。
方法返回值类型:
返回值可以是java允许的任何返回类型
必须使用void来声明没有返回值
static 是一个特殊的关键字,它可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于单个实例,因此通常把static修饰的成员变量称为类变量、类方法(可以直接调用,不需要创建对象)
不使用static修饰的普通方法,成员变量则属于该类的单个实例,而不属于该类。因为通常把不使用static修饰的成员变量和方法也成为实例变量和实例方法(需要对象才可以调用)
Attention:
static的真正作用是用于区分成员变量、方法,内部类、初始化块
构造器是一个特殊的方法,定义构造器的语法格式与定义方法的语法格式很想
{修饰符} 构造器名(形参列表) { //由0——多条可执行语句组成的构造器执行体 }
修饰符:修饰符可以省略,省略自动为public
ATTENTION:
构造器没有返回类型,如果为构造器定义了返回值类型,或使用void声明构造器没有返回值,编译时不会出错,但java会把这个所谓的构造器当作方法来处理
构造器返回的是类的实例,不能在构造器里显式使用return来返回当前类的对象,因为构造器的返回是隐式的
5.1.2 对象的产生和使用
创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例
var p = new Person(); //使用var可以提高简洁性
java对象的作用?
访问对象的实例变量
调用对象的方法
5.1.3 对象、引用和指针
Person p = new Person();
这行代码产生了两个东西:一个是变量p,一个是对象Person
Person对象含有两个变量,变量需要内存来存储的
Person对象由多块内存组成,不同内存块分别存储了Person对象的不同成员变量,当把这个Person对象赋给一个引用变量p时,会让引用变量直接指向这个对象。也就是说,引用变量里存放的仅仅是一个引用,它指向实际的对象
堆内存里的对象可以有多个引用,即多个引用变量指向同一个对象
var p2= p;//将p变量保存的地址值赋给p2变量
如果希望通知垃圾回收期回收某个对象,只需切断该对象的所有引用变量和它之间的关系即可
5.1.4 对象的this引用
this关键字指向调用该方法的对象
public class DOg{ public void jump(){ System.out.println("正在执行jump方法"); } public void run(){ jump(); System.out.println("正在执行run方法"); } } //一般来说,如果调用static修饰的成员(包括方法、成员变量)时省略了前面的主调,那么默认使用该类作为主调;如果调用没有static修饰的成员(包括方法、成员变量)时省略了前面的主调,那么默认使用this作为主调。
ATTENTION:
在Java中,我们应尽量使用类名来调用static方法,虽然对象也可以使用static方法
5.2 方法详解:
方法类似于函数,但是java中方法不能单独存在,方法在逻辑上属于类,要么属于对象。
5.2.1 方法的所属性
方法与传统的函数有着显著的不同;
在结构化编程语言中,函数是一等公民,整个软件由一个个函数组成。
但是在面向对象编程语言中,类才是一等公民,整个系统由一个个的类组成。
10.17
5.2.2方法的参数传递机制
调用方法时
Java中参数传递方式只有一种:值传递,就是将实际参数值的副本传入方法内,而参数本身不会受到任何影响
传入方法的实际参数值的复制品,不管方法中对这个复制品如何操作,实际参数值本身不会受到任何影响
//primitive 粗糙的 public class PrimitiveTransferTest { public static void swap(int a,int b){ var tmp = a; a = b; b = tmp; System.out.println("In method of swap:the value a is "+a"\n The value b is" +b); } public static void main(String[] args){ var a = 6; var b = 9; swap(a,b); System.out.println("After transfering,the va is"+a+"\n vb is "+b); } }
当程序执行swap()method,系统进入swap()方法,并将main()方法中的a、b变量作为参数值传入swap()方法,传入swap()方法只是a、b副本,而不是a,b本身
下面看一个容易搞混的点:传入引用变量
堆内存中保存了对象本身,栈内存中保存了引用该对象的引用变量
系统复制的时dw变量,而不是对象本身
变量是用来指向变量的指针
5.2.3 形参个数可变的方法
形参个数可变的参数本质就是一个数组参数
public static void test(int a,String...books) //equals public static void test(int a,String[] books)
区别是调用两个方法存在区别
第一种方法可以直接
test(5,"a","b"); //第二中method test(5,new String[]{"a","b"});
个数可变的形参只能处于参数列表的最后。一个方法最多只包含一个个数可变的形参。个数可变的形参本质上是一个数组类型的形参。因此调用包含个数可变形参的方法时,该个数可变的形参既可以传入多个参数,也可以传入一个数组。
######
5.2.5 方法重载
Java允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含两个或两个以上方法名相同,但形参列表不同,则被称为方法重载。
方法重载的要求就是两同一不同:同一个类中方法名相同,参数列表不同。至于方法的其他部分,如方法返回值类型、修饰符等,与方法重载没有任何关系。
5.3 成员变量和局部变量
成员变量指的是在类中定义的变量,也就是field
局部变量指的是在方法里定义的变量
一个类在使用之前要经过类加载、类验证、类准备、类解析、类初始化等几个阶段
类变量可以理解为类成员变量,与类本身共存亡
实例变量与实例共存亡
实例.类变量 实例.实例变量 实例.类变量
局部变量根据定义形式的不同,又被分为三种
形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效
方法局部变量:从定义该变量的地方生效,到该方法结束时生效
代码块局部变量:从定义该变量的地方生效,到该代码块结束时生效
5.3.2 成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。
var p1 = new Person(); var p2 = new Person(); p1.name = "Jack"; p2.name = "John"; p1.eyeName = 2; p2.eyeName = 3;
当程序执行第一行代码var p1=new Person();时,如果这行代码是第一次使用Person类,则系统通常会在第一次使用Person类时加载这个类,并初始化这个类。在类的准备阶段,系统将会为该类的类变量分配内存空间,并指定默认初始值。
当Person类初始化完成后,系统内存中的存储示意图如图5.10所示。
10.18
5.3.3 局部变量的初始化和内存中的运行机制
这里要注意一下
public static void main(String[] args){ int a;//这也是局部变量,因为这也是一个方法,修饰符是public static }
局部变量只有赋值了,系统才会给他内存
局部变量总是保存在其所在方法的栈内存中。如果局部变量是基本数据类型的变量,则直接把这个变量的值保存在该变量对应的内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组
栈内存中的变量无需垃圾回收器回收,往往随着方法或代码块的运行结束而结束
5.3.4 变量的使用规则
考虑使用成员变量:
1.固有信息->身高,每个人不同,但都有这么一个属性
2.类相关信息->眼睛的数量,大部分人有两只眼睛,这种一般不会改变的,可以修改成类变量
3.状态信息->五子棋的棋盘数组
4.共享信息
5.4 隐藏和封装
封装的作用,这里了解一下就行
➢ 隐藏类的实现细节。 ➢ 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里加入控制逻辑,限制对成员变量的不合理访问。 ➢ 可进行数据检查,从而有利于保证对象信息的完整性。 ➢ 便于修改,提高代码的可维护性。 为了实现良好的封装,需要从两个方面考虑。 ➢ 将对象的成员变量和实现细节隐藏起来,不允许外部直接访问。 ➢ 把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作
5.4.2 使用访问控制符
private:封装专用于修饰成员变量
default:在同一个包中
protected:子类也可以使用,通常用于要被继承的方法
public:被所有类访问
外部类只有两种:public 和 default 因为外部类没有在任何类的内部
一个类就是一个小的模块,要尽量避免一个类和另一个类发生相关联系
高内聚(将模块中的内部数据、功能实现的隐藏细节给英藏起来)和低耦合(尽暴漏少量的方法给外部使用)
关于访问修饰符的使用,存在如下规则:
1.有些方法只用于辅助实现该类的方法,也应该使用private
5.4.3 package、import和import static
在工作场合中,会出现一些同名类的情况,这个时候,包的出现可以有效的解决类的命名冲突,更方便我们找到我们需要的类文件
一旦源文件中使用了package语句,意味着该源文件中所有的类都属于这个包
操作系统在编译源文件的时候,会自动建立一个包,这个包里面有我们写的类文件
这是因为Java规定:位于包中的类,在文件系统中也必须有余包名层次相同的目录结构
package lee; public class Hello{ public static void main(String[] args){ System.out.println("我爱java"); } }
一个文件夹用于存放源文件,一个用来存放类文件
为Java指定包名,并不是由目录决定,是由我们编写的代码所决定的,package <包名>
包名应该全部是小写字母,而且是多个有意义的单词连缀而成的
为了避免不同公司之间类名的重复,Oracle建议使用公司Internet域名倒写来作为包名
eg:Internet:crazyit.org -> org.crazyit
在实际企业开发中,还会在org.carzyit包下面建立各种自爆;
如果项目足够大,还会在项目名子包中建立模块子包;反正一直到底层就对了
一个源文件只有一条package语句
为了在之后的编程中引用其他包下面的类没有那么繁琐,我们可以在开头引入指定包层次下的某个类或全部类
//import 语句应该出现在package语句之后、类定义之前 package lee; //导入指定类 import package.subpackage...ClassName; //导入全部类 import package.subpackage...*;
Attention:
Java serve all the file java that they all autonomic have java.lang all the classes
About Date.class
If you want to use class Date,please don't import java.sql and java.util at the same time,because they all have Date.class,or it will result into chaos(混乱的) in system
After JDK 1.5 . We can lead to certain class' single static variable,method and all static variable
//The syntax for importing all static member variables and methods of a specified class is as follows: //This sentence is meant to import static member variable and static method called methodName import static package.subpackage...ClassName.fieldName|methodName; // The syntax for importing all the static variables and method of specified class is as follows: import static package.subpackage...ClassName.*; //* only can symbolize the static member variable and method
10.19 5.4.4 Java package that in common use
java.lang:include corn class of Java
java.util:A large of tool/接口和集合框架类->Arrays and List
java.net:include class/API about Internet programme
java.io:include class/API about input/output
java.text:java formatting(格式化)
java.sql:structurct of JDBC
java.awt: class/API about Abstract Window Toolkits
java.swing:include Swing 图形用户界面编程,GUI程序
5.5 deep into constructor
The class of Java must include one or more than one constructor
-
5.1 Use constructor to initialize
If programmer don't provide any constructor to Java class,system will provide a constructor that don't have parameter
If constructor B about constructor A. We can use 'this' keyword
5.6 The inherit of class
Every class only have one straight parent class.(single inherit)
5.6.1 The characteristic of inherit
By 'extend'(扩展的意思)
被继承的类称为父类、超类、基类
从子类的角度来看 ,子类 extends 父类
父类角度,父类 derive(派生)了子类
5.6.2 重写父类的方法
方法的重写遵循“two same two small one large"
The name of method and 形参列表相同
两小:返回类型和抛出异常
一大:子类的访问权限>=父类的访问权限
Attention:
子类覆盖父类方法后,子类对象无法访问父类中被覆盖的方法。但是子类覆盖方法中可以调用父类被覆盖方法:
format:
super(被覆盖方法)
如果父类方法中定义了一个private 方法,子类具有一个与这个private方法相同的方法(所有都相同),不是重写,只是一个新方法
5.6.3 super 限定
//Using super to do the override method public void callOverrideMethod(){ //在子类方法中通过父类被覆盖的实例方法 super.fly(); }
super 用于限定该对象调用它从父类继承得到的实例变量或方法。
super不能出现在static修饰的方法,因为static修饰的方法是属于类的
如果在构造器中使用super,则super用于限定该构造器初始化的是该对象从父类继承得到的实例变量
class BaseClass{ public int a = 5; } public class SubClass extends BaseClass{ public int a =7; public void accessOwner(){ System.out.println(a); } public void accessBase(){ System.out.println(super.a); } public static void main(String[] args){ var sc = new SubClass(); //当系统创建了SubClass对象时,实际上会为SubClass对象分配两块内存,一块用于存储在SubClass对象分配两块内存,一块用于存储在SubClass类中定义的a实例变量,一块用于存储从BaseClass类继承得到的a实例变量 sc.accessOwner(); sc.accessBase(); } }
如果在某个方法中访问名为a的成员变量,但没有显示指定调用者,则系统查找a的顺序为: 1.查找该方法中是否有名为a的局部变量
2.查找当前类中是否包含名为a的成员变量
3.查找a的直接父类中是否包含名为a的成员变量,一次上溯a的所有父类,直到Object
class Parent{ public String tag = "疯狂Java讲义"; } class Derived extends Parent{ private String tag = "轻量级Java EE企业应用实战"; } public class HideTest{ public static void main(String[] args){ var d = new Derived(); System.out.println(((Parent)d).tag); } }
5.6.4 调用父类构造器
不管是否调用super关键字,子类构造器always调用父类构造器一次
子类构造器调用父类构造器分如下几种情况:
1.子类构造器执行体的第一行使用super显式调用父类构造器
2.子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,system将根据this调用传入的实参列表调用本类中的另一个构造器。执行本类中的另一个构造器时也会先调用父类构造器
3.子类构造器执行体中没有super和this调用,会首先隐式调用父类的无参数构造器
子类构造器在初始化子类对象时,父类构造器总在子类构造器之前执行。创建任何Java对象,最先执行的总是java.lang.Object类的构造器
5.7 多态
Java引用变量有两个类型:一个时编译类型,一个是运行类型。
因为子类其实是一种特殊的父类,Java允许把一个子类对象直接赋给一个父类引用变量,称为向上转型。
//编译类型 对象 = 运行类型 BaseClass ploy = new SubClass();//只能调用Subclass 的方法 BaseClass a2 = new BaseClass(); //相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态 //如果子类重写了父类的方法,那么我们在调用这个重写的方法的时候,会执行子类重写的方法,而不是父类。还有一种情况调用了只有子类有的方法,无法执行,会报错 /* 与方法不同,ploy调用父类的实例变量(如果父类和子类同时具有相同的实例变量时)就是父类的实例变量 */ //使用var并不能改变变量的类型 var v2 = ploy;
ATTENTION:
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。
Object a = new Tatto(); //只能调用Object所具有的方法 //v2也会发生多态
5.7.2 引用变量的强制类型转换
如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换成运行时类型,强制类型转化需要借助类型转化运算符。
form:
(type)variable
基本类型转换只在数值类型之间,数值类型和浮点数之间不能进行转换
引用类型之间的转换只能字具有继承关系的两个类型之间进行转换,如果要将一个父类实例转换为一个子类实例,必须编译时类型为父类类型,而运行时类型是子类类型,否则会引起ClassCastException异常
为了避免出现异常可以使用instanceof 运算符来判断是否可以成功转换
if (ObjPri instanceof String){ var str = (String)objPri; }
Attention:
当把子类对象赋给父类引用变量的时候,被称为向上转型,说明子类是特殊的父类
但把一个父类对象赋给子类引用变量的时候,就需要进行强制类型转换
5.7.3 instanceof 运算符
instanceof:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以成功转换,从而保证代码更加健壮。
5.8 继承与组合
继承破坏了类的封装型
父类的构造器中不要写被子类重写的类,否则会变成调用被子类重写的方法
5.8.2 利用组合实现复用
class Animal{ private void beat(){ System.out.println("心脏跳动"); } private void breathe(){ System.out.prinltn("Take a breath..."); } } class Bird{ //将原本的父类组合到原来的子类,作为子类的一个组合部分 private Animal a; public Bird(Animal a){ this.a = a; } //重新定义一个自己的breath()方法 public void breathe(){ //直接复用 a.breathe(); } public void fly(){ System.out.println("Flying freedom"); } }
此时的Bird对象由Animal组合而成,因此在创建Bird对象之前先创建Animal对象
继承表达的是 "is a"的关系
组合表达的是"has a"的关系
5.9 初始化块
5.9.1 use it
一个类可以有多个初始化块,前面定义的初始化块先执行。初始化块的语法格式如下:
static{ }//修饰符只能是static
没有static修饰的初始化块被称为实例初始化块
实例初始化块只在创建Java对象时隐式执行,而且在构造器执行之前自动执行。
当java创建一个对象时,系统先对该对象的所有实例变量分配内存(前提时该类已经被加载过了)->对实例变量执行初始化,其初始化顺序是:
先执行实例初始化块或声明实例变量时指定的初始值(这两个地方指定初始值的执行顺序与它们在源代码中的排列顺序相同),再执行构造器里指定的初始值
5.9.2 实例初始化块和构造器
实例初始化块是构造器的补充。
与构造器不同的时,实例初始化块是一段固定执行的代码。
如果一段初始化块处理代码对所有对象完全相同,且无须接收任何参数,就可以把这段初始化块代码提取到初始化块中
实际上实例初始化块是一个假象,用javac命令编译java类后,该java类中的实例初始化块会消失——实例初始化块中代码会被插入到每个构造器代码的最前面
与构造器类似,创建一个Java对象时,实例代码块也会从java.lang.Object依次向下执行
如果希望类加载后对整个类进行某些初始化块操作->类初始化块->static修饰初始化块
5.9.3 类初始化块
实例初始化块负责对对象执行初始化块,类初始化块则负责对类进行初始化块。
和实例初始化块相同,也会追溯到java.lang.Object以上
输出结果:
第六章 :面向对象(下)
6.1 包装类
为了解决8种基本数据类型的变量不能当成Object类型变量使用的问题,Java提供了包装类的概念
JDK1.5之后提供了自动装箱和自动拆箱功能。所谓自动 装箱就是可以 把一个基本类型变量直接赋给对应的 包装类变量,或者赋给Object变量
自动拆箱则允许直接把包装类对象直接赋给一个对应的基本类型变量
我们来看一下java.lang.Integer类的源代码 :
static final Integer[] cache = new Integer[-(-128)+127+1]; static{ //执行初始化,创建-128到127的Integer的实例,并放入cache数组中 for(int i = 0;i<cache.length;i++){ cache[i] = new INteger(i-128); } } /** 从这里咱们就可以看出java对Integer的设计就是将-128~127之间的整数自动装箱成实例,并且放入数组中缓存起来,实际上直接指向对应的数组元素 */
6.2 处理对象
//假设前面有个类 public class PrintObject{ public static void main(String[] args){ var p = new Person("孙悟空"); //打印p所引用的Person对象 System.out.println(p); } }
运行结果:
Person@15db9743
explanation to the outcome:
当我们使用System.out.println()输出Person对象时,实际上输出的是Person对象的toString()方法的返回值
toString()方法是Object 类里的一个实例方法,所以全部的Java对象都具有toString()方法
Object类提供的toString()方法总是返回该对象实现类的”类名+@+hashCode“值,但是toString方法通常会被人作为介绍对象的方法
假如说一个对象有信息要介绍
form:
类名[field1 = 值1,field2=值2,...] public String toString(){ return "Apple[color="+color+",weight=" +weight+"]"; }
6.2.2 == 和equals方法
对于==
如果两个变量都是基本类型,且都是数值类型(不要求数值类型完全相同),则只要两个变量的值相同,就返回true
int it = 65; System.out.println(it=='A');//返回true
我们来解释一下“hello” and new String("hello") 的区别:
当java程序直接使用形如"hello"的字符串常量时,JVM会使用常量池来管理这些字符串,而使用new String("hello"),JVM会先使用常量池来管理“hello”直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象将保存再堆内存中,所以一共产生了两个字符串对象。
JVM常量池将保证相同的字符串直接量只有一个,这样他们都将引用常量池中的同一个字符串对象
equls()方法是Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否与其他引用变量相同,其实和==没什么区别
如果希望采用自定义的标准,可以重写
接下来需要理解下面这段代码:
class Person{ private String name; private String idStr; public Person(){} public Person(String name,String idStr){ this.name = name; this.idStr = idStr; } //忽略setter and getter ... //override the method of equeals() public boolean equals(Object obj){ //如果两个对象是同一个对象 if(this == obj){ return true; } if(obj!=null && obj.getClass() == Person.class){ var personObj = (Person)obj; if(this.getIdStr().equals(personObj.getIdStr())){ return true; } } return false } }
6.3 类成员
用static修饰的类成员属于整个类,不属于单个实例
6.3.1 理解类成员
在Java类中只能包含5种成员 ->成员变量、方法、构造器、初始化块、内部类(包括接口,枚举)5种成员
但通过对象来访问类变量时,系统会在底层转化为通过该类来访问类变量
null对象不能访问实例成员,将会引发NullPointerException异常,因为null表明该实例根本不存在!但是可以访问类成员
6.3.2 单例类
如果一个类始终只能创建一个实例,则这个类被称为单例类。
class Singleton{ //使用一个类变量来缓存曾经创建的实例 private static Singleton Instance; //对构造器使用private修饰,隐藏构造器 private Singleton(){} //该方法可以自定义 public static Singleteon getInstance(){ //保证不会创建新的实例 if(Instance == null){ Instance = new Singleton(); } return instance; } }
6.4 final 修饰符
final修饰的成员变量必须有程序员显式地指定初始值
public class FinalVariableTest { final int a = 6;//normal final String str; final int c; fianl static double d;// like this,在之后的构造器或者代码块中一定要赋初始值 //final char ch;不赋初始值就会报错 //在初始化块中可对没有指定默认值的实例变量指定初始值 { str = "Hello"; //a = 9 ->报错 } //静态代码块可对没有赋值的类变量指定初始值 static { d = 5.6; } //构造器可对final实例变量(没有初始值)赋值 public FinalVariableTest(){ //如果在初始化块中已经对str指定初始值 c = 5; } public void changeFinal(){ //普通方法不能对final修饰的成员变量赋值 } }
6.4.3 final修饰基本类型变量和引用类型变量的区别
基本类型变量不能别改变,对于引用类型便来给你,保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象可以改变
看下代码,秒懂
class Person { private int age; public Person(int age){ this.age = age; } } public class FinalReferenceTest{ publlic static void main(String[] args){ //final修饰数组变量,iArr是一个引用变量 final int[] iArr = [5,6,12,9]; System.out.println(Arrays.toString(iArr)); iArr[2] = 2;//legal //iArr = null;//illegal final var p = new Person(45); p.setAge(23); //p = null; illegal } }
6.4.4 可执行“宏替换”的final变量
对于一个final变量中,只要满足三点,就是直接量,还不如直接看特例
final var a =3; System.out.println(a);//the same as sop(3);
有一种情况不会变成宏变量,就是调用了方法
final var book2 = "疯狂讲义"+String.valueOf(99);
或者是访问了普通变量
var s1 = "疯狂java"; var str1 = "疯狂"; var str2 = "java"; var s3 = str1 +str2; System,out.println(s3 == s1);
s1在编译时候确定,而str1 and str2 是两个普通变量,编译器不会执行“宏替换”,因此无法确定s3的值
6.4.5 final方法
public class FinalOverload{ //final修饰的方法只是不能被重写,完全可以被重载 public final void test(){} public final void test(String args){} }
//演示不可变量 public class Address{ //这其实就是两个不可变的量 private final String detail; private final String postCode; //在构造器里初始化两个实例变量 public Address(String detail,String postCode){ this.detail = detail; this.postCode = postCode; } //创建完之后就无法修改该Address对象的实例变量的值 }
设计一个不可变类
如果需要设计一个不可变类,尤其要注意其引用类型的成员变量的类是可变的,如果引用类型的成员变量是可变的,就必须采用必要的措施来保护该成员变量所引用的对象不会被修改
class Name { private String firstName; private String lastName; public Name(){} public Name(String firstName,String lastName){ this.firstName = firstName; this.lastName = lastName; } //省略firstName,lastName的getter和seetter方法 } public class Person { private final Name name; /* public Person(Name name){ this.name = name; } public Name getName(){ return name; } public static void main(String[] args){ var n = new Name("悟空","孙"); var p = new Person(n); System.out.println(p.getame().getFirstName()); n.setFirstName("八戒");//这样就破坏了不可变类的性质了 } } */ //所以我们可以新定一个对象,这个name对象是独属于Person类的 public class Person{ private final Name name; public Person(Name name){ //设置name实例变量为临时创建的Name对象,该对象与传入的name参数相同,这样就算改变原先name的属性值也没有任何用,因为指向的对象不一样 this.name = new Name(name.getFirstName(),name.getLastNmae()); } public Name getName(){ //返回一个匿名对象,该对象的firstName和LastName相同 return new Name(name.getFirstName(name.getLastName(),name.getLastName()); } }
6.5.1 抽象方法和抽象类
有抽象方法的类只能被定义成抽象类,抽象类里没有抽象方法
定义抽象方法不需要有花括号
public abstract class Shape{ { System.out.println("执行Shape的初始化块..."); } private String color; public abstract double calPerimerter(); public Shape(){}//定义Shape的构造器,该构造器不是用于创建Shape对象 //只是被子类继承 }