文章目录
- 1. 数据类型、变量与常量
- 1.1基本数据类型(primitive types)
- 1.1.1 boolean
- 1.1.2字符型
- 1.1.2.1 转 义 字 符 含 义
- 1.1.3整数类型
- 1.1.4 浮点类型
- 1.2引用类型( reference types )
- 1.3 基本数据类型和引用类型区别
- 1.4 标识符( Identifier)
- 2. 运算符与表达式
- 2.1运算符
- 2.1.1具体说明
- 3. 流程控制语句
- 4. 数组
- 1 .类、字段、方法
- 1.1定义
- 1.2 构造方法
- 1.3 使用对象
- 1.4方法重载( overload)
- 1.5 this的使用
- 2 .类的继承
- 2.1字段
- 2.2方法
- 2.3 super 的使用
- 2.4 父类对象与子类对象的转换
- 2.5例子
- 3 .包
- 3.1 包的定义
- 3.2 package 语句
- 3.3 import 语句
- 3.4 编译和运行包中的类
- 3.5 CLASSPATH
- 4 .访问控制符
- 4.1 修饰符
- 4.2 成员的访问控制符(权限修饰符)
- 4.3 类的访问控制符
- 4.4 setter 与getter
- 5 .非访问控制符
- 5.1 static
- 5.1.1 static 字段
- 5.1.2 static 方法
- 5.1.3 import static
- 5.2 final
- 5.3 abstract
- 6 .接口
- 6.1 定义
- 6.2 接口的作用
- 6.3 接口的实现
- 6.4 接口类型
- 6.5 接口中的常量
- 7 .枚举
- 8. Java8 中的接口
- 1 . 变量及其传递
- 1.1 基本类型变量与引用型变量
- 1.2 字段变量与局部变量
- 1.2 变量的传递
- 1.3变量的返回
- 不定长参数
- 2 . 多态和虚方法调用
- 2.1多态
- 2.2上溯造型
- 2.3虚方法调用
- 3 . 对象构造与初始化
- 3.1 构造方法(constructor)
- 3.2 创建对象时初始化
- 3.3 实例初始化与静态初始化
- 3.4 构造方法的执行过程
- 4 . 对象清除与垃圾回收
- 4.1 对象的自动清除
- System.gc ()方法
- finalize() 方法
- try -with-resources
- 5 . 内部类与匿名类
- 定义
- 内部类(Inner class)
- 局部类
- 匿名类
- 6 . Lambda表达式
- 7 . 装箱、枚举、注解
- 基本类型的包装类
- 装箱与拆箱
- 枚举
- 注解
- 8 . 没有指针的Java语言
- java中相等还是不等
1. 数据类型、变量与常量
数据类型决定数据的存储方式和运算方式
Java中的数据类型分为两大类
1.1基本数据类型(primitive types)
Java中定义了四类/八种基本数据类型
整数型---- byte, short, int, long
浮点数型---- float, double
逻辑型---- boolean
字符型---- char
1.1.1 boolean
boolean类型适于逻辑运算,一般用于程序流程控制
boolean类型数据只允许取值true或false,不可以0或非0的整数替代true和false
if(a=5)在java中是不允许的
用法举例:
boolean b = false;
if(b==true) {
//do
}
1.1.2字符型
char型数据用来表示通常意义上“字符”
字符常量是用单引号括起来的单个字符
char c = ‘A’;
Java字符采用Unicode编码,每个字符占两个字节,可用十六进制编码形式表示 char c1 = ‘\u0061’;
Java语言中还允许使用转义字符’'来将其后的字符转变为其它的含义
char c2 = ‘\n’; //代表换行符
1.1.2.1 转 义 字 符 含 义
\ddd 1到3位八进制数所表示的字符(ddd)
\uxxxx 1到4位十六进制数所表示的字符(xxxx)
\'
单引号字符
\"
双引号字符
\ 反斜杠字符
\r 回车
\n 换行
\f 走纸换页
\t 横向跳格
\b 退格
1.1.3整数类型
Java各整数类型有固定的表数范围和字段长度,而不受具体操作系统的 影响,以保证Java程序的可移植性
byte 1字节 -128 ~ 127
short 2字节 -2^15 ~ 2 ^15-1
int 4字节 -2 ^ 31 ~ 2^31-1
long 8字节 -2^63 ~ 2 ^63-1
Java语言整型常量的三种表示形式:
十进制整数,如12, -314, 0。
八进制整数,要求以0开头,如012
十六进制数,要求0x或0X开头,如0x12
二进制数,以0b或0B开头,如0b00010010 (Java7以上)
Java语言的整型常量默认为int型,
如: int i =3;
声明long型常量可以后加‘ l ’或‘ L ’ ,
如:long l = 3L;
Java中没有“无符号数” 可以用long来处理无符号整数(uint)
1.1.4 浮点类型
Java浮点类型有固定的表数范围和字段长度
float 4字节 -3.403E38~3.403E38
double 8字节 -1.798E308~1.798E308
Java浮点类型常量有两种表示形式
十进制数形式,必须含有小数点,
例如: 3.14 314.0 .314
Java7以上: 123_456.789_000 (千分位分割符用下划线表示)
科学记数法形式,如 3.14e2 3.14E2 314E2
Java浮点型常量默认为double型, 如要声明一个常量为float型,则需在数字后面加f或F,
如: double d = 3.14; float f = 3.14f;
1.2引用类型( reference types )
类(class)
接口(interface)
数组
1.3 基本数据类型和引用类型区别
基本类型: 变量在栈,在“这里”
引用类型: 变量引用到堆,在“那里”
double d = 3; Person p = new Person();
赋值时 double d2 = d; 复制的是值
Person p2 = p; 复制的是引用
1.4 标识符( Identifier)
名字就是标识符:任何一个变量、常量、方法、对象和类都需要有名字。
标识符要满足如下的规定:
(1)标识符可以由字母、数字和下划线(_)、美元符号($)组合而成; (2)标识符必须以字母、下划线或美元符号开头,不能以数字开头。
标识符最好与其意义相符,以增加程序的可读性
应注意Java是大小写敏感的语言。
按Java惯例,
1.类名首字母用大写(Pascal)
2.其余的(包名、方法名、变量名)首字母都小写(camel)
3.少用下划线
4.变量、常量随使用随定义
2. 运算符与表达式
2.1运算符
算术运算符: +,―,,/,%,++,-
关系运算符: >,<,>=,<=,==,!=
逻辑运算符: !,& , | , ^ , &&,||
位运算符: &,|,^,~ , >>,<<,>>>
赋值运算符: =
扩展赋值运算符:+=,―=,=,/=
字符串连接运算符: +
2.1.1具体说明
左移 "a<<b; "将二进制形式的a逐位左移b位,最低位空出的b位补0;
带符号右移 "a>>b; "将二进制形式的a逐位右移b位,最高位空出的b位补原来的符号位;
无符号右移 "a>>>b;"将二进制形式的a逐位右移b位,最高位空出的b位补0
移位运算符性质
适用数据类型:byte、short、char、int、long
对低于int型的操作数将先自动转换为int型再移位(整型提升,对所有的运算 都是这样)
对于int型整数移位a>>b,系统先将b对32取模,得到的结果才是真正移位的 位数
对于long型整数移位时a>>b ,则是先将移位位数b对64取模
赋值运算符=
当“=”两侧的数据类型不一致时,可以适用默认类型转换或强制类型转换 (casting)原则进行处理
long ll = 100; int i = (int)ll;
特例:可以将整型常量直接赋值给byte, short, char等类型变量,而不需要进 行强制类型转换,只要不超出其表数范围
byte b = 12; //合法 byte b = 4096; //非法
"+" 除用于算术加法运算外,还可用于对字符串进行连接操作
int i = 300 +5;
String s = "hello, " + “world!”;
"+"运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一 个操作数转换为字符串然后再进行连接
int i = 300 +5; String s = "hello, " + i + "号";System.out.println(s); //输出:hello, 305号
表达式
表达式是符合一定语法规则的运算符和操作数的序列
a
5.0 + a
(a-b)*c-4
i<30 && i%10!=0
表达式的类型和值
对表达式中操作数进行运算得到的结果称为表达式的值
表达式的值的数据类型即为表达式的类型
表达式的运算顺序
首先应按照运算符的优先级从高到低的顺序进行
优先级相同的运算符按照事先约定的结合方向进行
当有不同种类的混合运算时:
int->long->float->double
整型提升
所有的byte, short, char 参与算术运算等转为int
3. 流程控制语句
没有“表达式语句”这个概念 x+y;是不合法的
方法调用语句
赋值语句,注意分号(;)
分支语句
循环语句
类似c++
4. 数组
数组是多个相同类型数据的组合
一维数组的声明方式:
int[] a;
double []b
Mydate []c;
注意方括号写到变量名的前面,也可以写到后面
数组定义 与 为数组元素分配空间 分开进行
Java语言中声明数组时不能指定其长度(数组中元素的个数),
例如: int a[5]; //非法
数组是引用类型
int [ ] a = new int[5];
这里 a 只是一个引用
静态初始化:
在定义数组的同时就为数组元素分配空间并赋值。
int[] a = { 3, 9, 8};
或写为 int[] a = new int[]{ 3, 9, 8 };MyDate[] dates= {
new MyDate(22, 7, 1964),
new MyDate(1, 1, 2000),
new MyDate(22, 12, 1964)
};
默认初始化
数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被 隐式初始化。例如: ( 数值类型是0, 引用类型是null )
int []a= new int[5]; //a[3]则是0
数组元素的引用
数组元素的引用方式
index为数组元素下标,可以是整型常量或整型表达式。
如a[3] , b[i] , c[6*i];
数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 ~ n-1;
每个数组都有一个属性length指明它的长度,
例如:a.length 指明数组a的 长度(元素个数);
int[] ages = new int[10]; for ( int i=0; i<ages.length; i++ ) { System.out.println( ages[i] ); }
Enhanced for语句可以方便地处理数组、集合中各元素
int[] ages = new int[10];
for ( int age : ages )
{ System.out.println( age );
} //这种语句是只读式的遍历
System.arraycopy方法提供了数组元素复制功能:
//源数组 int[] source = { 1, 2, 3, 4, 5, 6 }; // 目的数组 int []dest = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; // 复制源数组中从下标0开始的source.length个元素到目的数组,//从下标0的位置开始存储。 // System.arraycopy( source, 0, dest, 0, source.Length );
public static void arraycopy(Object src,int srcPos,Object dest,int destPos, int length)
其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度。
二维数组
int [][] t = new int [3][];
t[0] = new int[2];
t[1] = new int[4];
t[2] = new int[3];
多维数组的声明和初始化应按从高维到低维的顺序进行
int t1[][] = new int [][4]; // 非法 , 这与 C++ 不同
1 .类、字段、方法
1.1定义
类是组成Java程序的基本要素,
是一类对象的原型
它封装了一类对象的状态和方法
它将变量与函数封装到一个类中
class Person { String name; int age; void sayHello(){ System.out.println("Hello! My name is" + name ); }
}
字段(field)是类的属性,是用变量来表示的。
字段又称为域、域变量、属性、成员变量等
方法(method)是类的功能和操作, 是用函数来表示的
1.2 构造方法
构造方法(constructor )是一种特殊的方法
用来初始化(new)该类的一个新的对象
构造方法和类名同名,而且不写返回数据类型。
Person( String n, int a ){ name = n; age = a; }
一般情况下,类都有一个至多个构造方法
如果没有定义任何构造方法,系统会自动产生一个构造方法,称为默 认构造方法(default constructor)。
默认构造方法不带参数,并且方法体为空。
1.3 使用对象
访问对象的字段或方法,需要用算符“.” :
Person p = new Person(); System.out.println( p.name ); p.sayHello();
这种使用方式的好处 封装性 安全性
1.4方法重载( overload)
方法重载(overloading):多个方法有相同的名字,编译时能识别出 来。
这些方法的签名(signature)不同,或者是参数个数不同,或者是参 数类型不同。
通过方法重载可以实现多态(polymorphism)
1.5 this的使用
1.在方法及构造方法中,使用this来访问字段及方法
例如,方法sayHello中使用name和使用this.name是相同的。即:
void sayHello(){ System.out.println("Hello! My name is " + name ); } 与 void sayHello(){ System.out.println("Hello! My name is " + this.name ); } 的含义是相同的。
this指当前对象实例本身
2.使用this解决局部变量与域同名的问题
使用this还可以解决局部变量(方法中的变量)或参数变量与域变 量同名的问题。如,在构造方法中,经常这样用:
Person( int age, String name ) { this.age = age; //前一个是当前字段的age,后面是表示当前的局部变量int agethis.name = name; }
这里,this.age表示域变量,而age表示的是参数变量。
3.构造方法中,用this调用另一构造方法
构造方法中,还可以用this来调用另一构造方法。如:
Person( ) { this( 0, "" ); //一个构造方法中调用另一个构造方法…… }
在构造方法中调用另一构造方法,则这条调用语句必须放在第一句。
2 .类的继承
继承(inheritance)是面向对象的程序设计中最为重要的特征之一
子类(subclass),父类或超类(superclass)
父类包括所有直接或间接被继承的类
Java支持单继承:一个类只能有一个直接父类。
子类继承父类的状态和行为
可以修改父类的状态或重载父类的行为
可以添加新的状态和行为。
好处
可以提高程序的抽象程度
实现代码重用,提高开发效率和可维护性
Java中的继承是通过extends关键字来实现的
class Student extends Person {
……
}
如果没有extends子句,则该类默认为java.lang.Object的子类。
所有的类都是通过直接或间接地继承java.lang.Object得到的。
所有对象都可以用他的 toString方法转换成字符串,都可以克隆
类Student从类Person继承:
class Student extends Person { String school; int score; boolean isGood(){ return score>80; } //… }
2.1字段
1.字段的继承
子类可以继承父类的所有字段
Student自动具有Person的属性(name,age)
2. 字段的隐藏
子类重新定义一个与从父类那里继承来的域变量完全相同的变量,称为域的隐藏。域的隐藏在实际编程中用得 较少。
3. 字段的添加
在定义子类时,加上新的域变量,就可以使子类比父类多一些属性。如:在定义子类时,加上新的域变量,就可以使子类比父类多一些属性。如:
class Student extends Person
{ String school; int score;
}
2.2方法
1.方法的继承
父类的非私有方法也可以被子类自动继承。如,Student自动继承Person的方 法sayHello和isOlderThan。
2.方法的覆盖(Override)(修改)
子类也可以重新定义与父类同名的方法,实现对父类方法的覆盖
void sayHello(){System.out.println("Hello! My name is " + name + ". My school is " + school );}
通过方法的覆盖,能够修改对象的同名方法的具体实现方法。
3.方法的添加
子类可以新加一些方法,以针对子类实现相应的功能。
如,在类Student中,加入一个方法,对分数进行判断:
boolean isGoodStudent(){
return score>=90;}
4 . 方法的重载
一个类中可以有几个同名的方法,这称为方法的重载(Overload)。
同时,还可以重载父类的同名方法。
与方法覆盖不同的是,重载不要求参数 类型列表相同。
重载的方法实际是新加的方法。
如,在类Student中,重载一个名为sayHello的方法:
void sayHello( Student another ){ System.out.println("Hi!"); if( school .equals( another.school )) System.out.println(" Shoolmates "); }
2.3 super 的使用
1.使用super访问父类的域和方法
注意:正是由于继承,使用this可以访问父类的域和方法。但有时为了明确地指明父类的域和方法,就要用关 键字super。
例如:父类Student有一个域age,在子类Student中用age, this.age, super.age来访问age是完全一样的:
void testThisSuper(){ int a; a = age; a = this.age; //是当前的agea = super.age;//指明了是父类的age}
当然,使用super不能访问在子类中添加的域和方法。
有时需要使用super以区别同名的域与方法
使用super可以访问被子类所隐藏了的同名变量。
又如,当覆盖父类的同名方法的同时,又要调用父类的方法,就必须使用super。
如:
void sayHello(){ super.sayHello(); System.out.println( "My school is " + school ); }
**在覆盖父类的方法的同时,又利用已定义好的父类的方法。**批判性的继承
2.使用父类的构造方法
构造方法是不能继承的
比如,父类Person有一个构造方法Person(String, int),不能说子类Student也自动有一个构造方法Student(String, int)。所以子类要从新定义构造方法
但是,子类在构造方法中,可以用super来调用父类的构造方法。
Student(String name, int age, String school ){ super( name, age ); this.school = school; }
使用时,super()必须放在第一句。
2.4 父类对象与子类对象的转换
类似于基本数据类型数据之间的强制类型转换,存在继承关系的父类对象和 子类对象之间也可以在一定条件下相互转换。
(1) 子类对象可以被视为其父类的一个对象
如一个Student对象也是一个Person对象。
(2) 父类对象不能被当做其某一个子类的对象。找一个学生不能随便拉来一个人
(3) 如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为实际参数。
需要一个人,实际传过来一个学生,也是可以的
(4) 如果父类对象引用指向的实际是一个子类对象,那么这个父类对象的引 用可以用强制类型转换(casting)成子类对象的引用。
2.5例子
class Person {
String name;
int age;Person( String n, int a ){name = n;age = a;
}Person( String n ){name = n;age = 0;
}Person( int age, String name )
{this.age = age;this.name = name;
}Person( ){this( 0, "" );
}boolean isOlderThan( int anAge ){return this.age > anAge;
}void sayHello(){System.out.println("Hello! My name is " + name );
}void sayHello( Person another ){System.out.println("Hello," + another.name + "! My name is " + name );
}public static void main(String[] args)
{Person p = new Person("Li Min", 18);Person p2 = new Person("Wang Qiang", 20 );p.sayHello();p.sayHello(p2);
}
}
class Student extends Person
{String school;int score;void sayHello( Student another ){System.out.println("Hi!");if( school == another.school ) System.out.println(" Shoolmates ");}boolean isGoodStudent(){return score>=90;}void sayHello(){super.sayHello();System.out.println( "My school is " + school );}Student(String name, int age, String school ){super( name, age );this.school = school;}Student(){}void testThisAndSuper(){int a;a = age;a = this.age;a = super.age;}public static void main( String [] arggs ){Person p = new Person( "Liming", 50 );Student s = new Student( "Wangqiang", 20, "PKU" );Person p2 = new Student( "Zhangyi", 18, "THU" );Student s2 = (Student) p2;//强制转换//Student s3 = (Student) p; //runtime exception p.sayHello( s );Person [] manypeople = new Person[ 100 ];manypeople[0] = new Person("Li", 18 );manypeople[1] = new Student("Wang", 18, "PKU");}
}
3 .包
3.1 包的定义
package pkg1[.pkg2[.pkg3…]];
包及子包的定义,实际上是为了解决名字空间、名字冲突
它与类的继承没有关系。
事实上,一个子类与其父类可以位于不同的包中。
包有两方面的含义
一是名字空间、存储路径(文件夹)、
一是可访问性(同一包中的各个类,默认情况下可互相访问)
3.2 package 语句
包层次的根目录是由环境变量CLASSPATH来确定的。
在简单情况下,没有package语句,这时称为无名包(unnamed package)
在Eclipse中,也叫(default package)。
Java的JDK提供了很多包
java.applet,java.awt,java.awt.image,java.awt.peer,java.io, java.lang,java.net,java.util,javax.swing,等
3.3 import 语句
为了能使用Java中已提供的类,需要用import语句来导入所需要的类。
import语句的格式为: import package1[.package2…]. (classname |*); 例如: import java.util.Date; 这样,程序中 java.util.Date可以简写为Date import java.awt.*; import java.awt.event.*; 注意:使用星号(*)只能表示本层次的所有类,不包括子层次下的类。
Java编译器自动导入包java.lang.* 所以不用写math类,
Eclipse等IDE可以方便地生成import语句
3.4 编译和运行包中的类
使用javac可以将.class文件放入到相应的目录,只需要使用一个命令选项-d来指明包的根目录即可。
javac -d d:\tang\ch04 d:\tang\ch04\pk\TestPkg.java
javac -d . pk*.java
其中,“.”表示当前目录
运行该程序,需要指明含有main的类名:
java pk.TestPkg
3.5 CLASSPATH
在编译和运行程序中,经常要用到多个包,怎样指明这些包的根目录呢?
简单地说,包层次的根目录是由环境变量CLASSPATH来确定的。
具体操作 有两种方法。
一是在java及javac命令行中,用-classpath(或-cp)选项来指明,如:
java –classpath d:\tang\ch04;c:\java\classes;. pk.TestPkg
二是设定classpath环境变量,用命令行设定环境变量,如:
set classpath= d:\tang\ch04;c:\java\classes;.
4 .访问控制符
4.1 修饰符
加到类的名字或字段的名字之前修饰一下
修饰符(modifiers)分为两类
访问修饰符(access modifiers)
如public/private等
其他修饰符
如abstract等
可以修饰类、也可以修饰类的成员(字段、方法)
4.2 成员的访问控制符(权限修饰符)
可见性,默认类似于c++里的友元,十分方便
同一个类中 | 同一个包中 | 不同包中的子类 | 不同包中的非子类 | |
---|---|---|---|---|
private | YES | |||
默认 (包可访问) | YES | YES | ||
protected | YES | YES | YES | |
public | YES | YES | YES | YES |
4.3 类的访问控制符
在定义类时,也可以用访问控制符。
类的访问控制符或者为public,或者默认。 没有private
若使用public,其格式为:
public class 类名{
……
}
如果类用public修饰,则该类可以被其他类所访问;
若类默认访问控制符,则该类只能被同包中的类访问。
4.4 setter 与getter
将字段用private修饰,从而更好地将信息进行封装和隐藏。
用setXXXX和getXXXX方法对类的属性进行存取,分别称为setter与getter。 字段是private,方法可以是public
这种方法有以下优点
(1)属性用private更好地封装和隐藏,外部类不能随意存取和修改。
(2)提供方法来存取对象的属性,在方法中可以对给定的参数的合法性进行检验。
(3)方法可以用来给出计算后的值。
(4)方法可以完成其他必要的工作(如清理资源、设定状态,等等)。
(5)只提供getXXXX方法,而不提供setXXXX方法,可以保证属性是只读的。无法修改
Setter/getter 示例
class Person2
{ private int age; public void setAge( int age ){ if (age>0 && age<200) this.age = age; } public int getAge(){ return age; }
}
尽量不要把字段直接暴露出来,这样就很好
5 .非访问控制符
基 本 含 义 | 修 饰 类 | 修 饰 成 员 | 修饰局部变量 | |
---|---|---|---|---|
static | 静态的、非实例的、类的 | 可以修饰内部类 | Yes | |
final | 最终的、不可改变的 | Yes | Yes | Yes |
abstract | 抽象的、不可实例化的 | Yes | Yes |
5.1 static
5.1.1 static 字段
静态字段最本质的特点是:
它们是类的字段,不属于任何一个对象实例。
它不保存在某个对象实例的内存区间中,而是保存在类的内存区域的公共存储单元。
类变量可以通过类名直接访问,也可以通过实例对象来访问,两种方法的结果是相同的。 实例对象也是属于某一个类的
如System类的in和out对象,就是属于类的域,直接用类名来访问, 即System.in和System.out。
例子
在类Person中可以定义一个类域为totalNum:
class Person { static long totalNum; int age; String Name;
}
totalNum代表人类的总人数,它与具体对象实例无关。可以有两种方法来 访问:Person.totalNum和p.totalNum (假定p是Person对象,实例)。
在一定意义上,可以用来表示全局变量
5.1.2 static 方法
用static修饰符修饰的方法仅属于类的静态方法,又称为类方法。
与此相对,不用static修饰的方法,则为实例方法。
类方法的本质是该方法是属于整个类的,不是属于某个实例的。
声明一个方法为static有以下几重含义。
(1) 非static的方法是属于某个对象的方法,在这个对象创建时,对象 的方法在内存中拥有自己专用的代码段。而static的方法是属于整个类 的,它在内存中的代码段将随着类的定义而进行分配和装载,不被任 何一个对象专有。
(2) 由于static方法是属于整个类的,所以它不能操纵和处理属于某个对象 的成员变量,而只能处理属于整个类的成员变量,即static方法只能处理本类中的static域或调用static方法。
(3) **static方法中,不能访问实例变量,不能使用this 或super。**因为this和super是具体的当前的某一个实例的,而static是属于整个类的
(4) 调用这个方法时,应该使用类名直接调用,也可以用某一个具体的对象 名。
例如:Math.random(),Integer.parseInt()等就是类方法,直接用类名进行访问,他不是实例方法
5.1.3 import static
import static java.lang.System.*;
有一个类成员都是static
out.println();表示System.out.println();
5.2 final
1.final类
如果一个类被final修饰符所修饰和限定,说明这个类不能被继承,即不可能有 子类。 可以进行优化
2.final方法
final修饰符所修饰的方法,是不能被子类所覆盖的方法。
3. final字段、final局部变量(方法中的变量)
它们的值一旦给定,就不能更改。
是只读量,它们能且只能被赋值一次,而不能被赋值多次。
一个字段被static final两个修饰符所限定时,它可以表示常量,
如Integer. MAX_VALUE(表示最大整数)、Math.PI(表示圆周率)就是这种常量。
关于赋值
1.在定义static final域时,若不给定初始值,则按默认值进行初始化(数值为0,boolean型为false,引用型为 null)。
2.在定义final字段时,若不是static的域,则必须且只能赋值一次,不能缺省。
这种域的赋值的方式有两种:一是在定义变量时赋初始值,二是在每一个构造函数中进行赋值。
3.在定义final局部变量时,也必须且只能赋值一次。它的值可能不是常量,但它的取值在变量存在期间不会改变。(只读的)
5.3 abstract
1 . abstract类
凡是用abstract修饰符修饰的类被称为抽象类。
抽象类不能被实例化 ,不能new一个实例对象
2.abstract方法
被abstract所修饰的方法叫抽象方法,抽象方法的作用在为所有子类定义一个统一的 接口。对抽象方法只需声明,而不需实现,即用分号(;)而不是用{},格式如下:
abstract returnType abstractMethod( [paramlist] );
抽象类中可以包含抽象方法,也可以不包含abstract方法。但是,一旦某个类中包含 了abstract方法,则这个类必须声明为abstract类。
抽象方法在子类中必须被实现,否则子类仍然是abstract的。
6 .接口
6.1 定义
接口,某种特征的约定 ,约定特征,引用类型
定义接口 interface
所有方法都自动是public abstract的 ,公开不考虑实现
这个特征可以被不同的类所实现,使用接口时候可以用某个具体对象代替他
实现接口 implements
可以实现多继承
与类的继承关系无关
面向接口编程,而不是面向实现
Flyable f = new Bird();
Java中有大量的接口
6.2 接口的作用
1 . 通过接口可以实现不相关类的相同行为,(超人,鸟,飞机相同行为是可飞行)而不需要考虑这些类之间 的层次关系。从而在一定意义上实现了多重继承。 (可以让一个类实现多个特征,可飞翔,可复制。。。)
2. 通过接口可以指明多个类需要实现的方法。
3. 通过接口可以了解对象的交互界面,而不需了解对象所对应的类。
示例
下面我们给出一个接口的定义: 实现方法都是public
interface Collection { void add (Object obj); void delete (Object obj); Object find (Object obj); int size ( ); }
通常接口以able或ible结尾,表明接口能完成一定的行为。
接口声明中还可以包括对接口的访问权限以及它的父接口列表。完整的接口声明如下:
[public] interface interfaceName [extends listOfSuperInterface]{
……
}
其中public指明任意类均可以使用这个接口,缺省情况下,只有与该接口定义在同一个包中的类才可以访问这个接口。
extends 子句与类声明中的extends子句基本相同,不同的是一个接口可以有多个父接口, 用逗号隔开,而一个类只能有一个父类。子接口继承父接口中所有的常量和方法。
方法定义的格式为:
returnType methodName ( [paramlist] );
接口中只进行方法的声明,而不提供方法的实现,所以,方法定义没有方法体,且用分号(;)结尾。在接口中声明的方法具有public 和 abstract属性。
所以定义的时候这两个关键词是可以省略的
另外,如果在子接口中定义了和父接口同名的常量或相同的方法,则 父接口中的常量被隐藏,方法被重载。
6.3 接口的实现
在类的声明中用implements子句来表示一个类使用某个接口,在类体中可以使用接口中定义的常量,而且必须实现接口中定义的所有方法。一个类可以实现多个接口。
下面我们在类FIFOQueue中实现上面所定义的接口collection:
class FIFOQueue implements collection{
public void add ( Object obj ){……}public void delete( Object obj ){……}public Object find( Object obj ){……}public int currentCount{……}
在类中实现接口所定义的方法时,方法的声明必须与接口中所定义的完全一致。
6.4 接口类型
接口可以作为一种引用类型来使用。任何实现该接口的类的实例都可以存储在该接口类型的变量中,通过这些变量可以访问类所实现的接口中的方法。Java运行时系统动态地确定该使用哪个类中的方法。
把接口作为一种数据类型可以不需要了解对象所对应的具体的类,以 前面所定义的接口Collection和实现该接口的类FIFOQueue为例,下例中,我们以Collection作为引用类型来使用。
public static void main( String args[] ){ Collection c = new FIFOQueue();…… c.add( obj ); …… }
6.5 接口中的常量
接口体中可以包含常量定义
常量定义的格式为:
type NAME = value;
其中type可以是任意类型,NAME是常量名,通常用大写,value是 常量值。
在接口中定义的常量可以被实现该接口的多个类共享,它与 C中用 #define以及C++中用const定义的常量是相同的。
在接口中定义的常量具有public, static, final的属性。(可以省略)
7 .枚举
从JDK1.5起,可以使用枚举
enum Light {
Red, Yellow, Green
}
使用
Ligth light = Light.Red;
switch( light ) {
case Red:
…… Break;
}
注意:case后面不写为 Light.Red
Java中的枚举是用class来实现的,可以复杂地使用
8. Java8 中的接口
Java8以上,接口成员还可以是:
static方法
具有实现体的方法 (default方法)
默认方法的好处是:提供了一个默认实现,子类在implements可以不用再重新写了
1 . 变量及其传递
1.1 基本类型变量与引用型变量
基本类型(primitive type):其值直接存于变量中。“在这里”
引用型(reference type) 的变量(class,interface,数组)除占据一定的内存空间外,它所引用 的对象实体(由new 创建)也要占据一定空间。“在那里”
引用变量在这里只是存一个对象实体的地址,通过这个引用能够操作这个对象
MyDate m,n;m=new MyDate();n=m;n.addYear();
.
public class MyDate {private int day;private int month;private int year;public MyDate(int y, int m, int d) {year = y;month = m;day = d;} void addYear(){year ++;}public void display() {System.out.println(year + "-" + month + "-" +day); }public static void main(String[] args) {MyDate m = new MyDate(2003, 9, 22);MyDate n = m;//复制只是复制了一个引用n.addYear();m.display();//操作的是同一个对象n.display();}
}
1.2 字段变量与局部变量
字段变量(field)与局部变量(Local variable)
前者是在类中,后者是方法中定义的变量或方法的参变量
从内存角度看
存储位置,字段变量为对象的一部分、存在于堆中的,局部变量是存在于栈中。
生命周期不同 字段变量随着对象的存在而存在,局部变量随着方法的存在而存在,随着方法的结束而结束
初始值:字段变量可以自动赋初值,局部变量则须显式赋值
class Test() { int a; void m(){ int b; System.out.println(b);//编译不能通过需要初始化。 } }
从语法角度看
字段变量属于类,可以用public,private,static,final 修饰。
局部变量不能够被访问控制符及static修饰
都可以被final修饰
1.2 变量的传递
调用对象方法时,要传递参数。在传递参数时,
Java 是值传递,即,是将表达式的值复制给形式参数。
对于引用型变量,传递的值是引用值,而不是复制对象实体
可以改变对象的属性
1.3变量的返回
方法的返回:
返回基本类型。
返回引用类型。它就可以存取对象实体。
Object getNewObject() { Object obj=new Object(); return obj; }
调用时:Object p= GetNewObject();
不定长参数
不定长参数(Variable length arguments),从JDK1.5开始
用省略号表示, 并且是最后一个参数
实际上Java当成一个数组
int sum( int … nums){ int s=0; for(int n : nums) s+=n; return s; }
调用:sum(1,2,3,4);
又例如: public static void main( String…argv)
2 . 多态和虚方法调用
2.1多态
多态(Polymorphism)是指一个程序中相同的名字表示不同的含义的情况。
多态有两种情形
编译时多态:
重载(overload) (多个同名的不同方法)。
如 p.sayHello(); p.sayHello(“Wang”);
运行时多态:
覆盖(override) (子类对父类方法进行覆盖)
动态绑定(dynamic binding) ----虚方法调用(virtual method invoking)
在调用方法时,程序会正确地调用子类对象的方法。
多态的特点大大提高了程序的抽象程度和简洁性
2.2上溯造型
上溯造型(upcasting)
是把派生类型当作基本类型处理
例子见下面博客
https://blog.csdn.net/weijie_home/article/details/49105871
Person p = new Student(); void fun(Person p ){…} fun(new Person());
2.3虚方法调用
什么是虚方法?
https://blog.csdn.net/vop444/article/details/69525124#commentBox
虚方法例子:
https://blog.csdn.net/qq_32863631/article/details/79227859
用虚方法调用,可以实现运行时的多态!
子类重载了父类方法时,运行时
运行时系统根据调用该方法的实例的类型(传进去的时student,那么就调用student)来决定选择哪个方法调用
所有的非final方法都会自动地进行动态绑定!
什么是动态绑定?
https://blog.csdn.net/javamoo/article/details/78776150
虚方法调用示例
class TestStaticInvoke
{static void doStuff( Shape s ){s.draw();}public static void main( String [] args ){Circle c = new Circle();Triangle t = new Triangle();Line l = new Line();doStuff(c);doStuff(t);doStuff(l);Shape s = new Circle();doStuff(s);s.draw();Circle c2 = new Circle();c2.draw();}
}
class Shape
{void draw(){ System.out.println("Shape Drawing"); }
}
class Circle extends Shape
{void draw(){ System.out.println("Draw Circle"); }
}class Triangle extends Shape
{void draw(){ System.out.println("Draw Three Lines"); }
}class Line extends Shape
{void draw(){ System.out.println("Draw Line"); }
}
//输出:
//Draw Circle
//Draw Three Lines
//Draw Line
//Draw Circle
//Draw Circle
//Draw Circle
动态类型确定
变量 instanceof 类型
结果是boolean 值(实际就是这个类型或者是他的子类型,则返回true)
对实际类型进行判断
例子:
class Instanceof
{public static void main(String[] args) {Object [] things = new Object[3];//object所有类的父类things[0] = new Integer(4);things[1] = new Double(3.14);things[2] = new String("2.09");double s = 0;for( int i=0; i<things.length; i++ ){if( things[i] instanceof Integer )//看看是不是属于这个类型s += ((Integer)things[i]).intValue();//强制类型转换else if( things[i] instanceof Double )s += ((Double)things[i]).doubleValue();}System.out.println("sum=" + s);}
}
//输出:sum=7.140000000000001
什么情况不是虚方法调用
Java中,普通的方法是虚方法
(在调用过程中会根据实际的对象来决定方法的调用)
但static,private方法不是虚方法调用 (static是属于类的,private是属于这个类自己的)
static,private与虚方法编译后用的指令是不同的
package text1;public class JavaP3methods {void f(){}private void p(){}static void s(){}public static void main(String...argv){JavaP3methods obj = new JavaP3methods();obj.f();obj.p();obj.s();}
}
反汇编代码:
Compiled from "JavaP3methods.java"
public class text1.JavaP3methods {public text1.JavaP3methods();Code:0: aload_01: invokespecial #8 // Method java/lang/Object."<init>":()V4: returnvoid f();Code:0: returnstatic void s();Code:0: returnpublic static void main(java.lang.String...);Code:0: new #1 // class text1/JavaP3methods3: dup4: invokespecial #19 // Method "<init>":()V7: astore_18: aload_19: invokevirtual #20 // Method f:()V12: aload_113: invokespecial #22 // Method p:()V16: invokestatic #24 // Method s:()V19: return
}
三种非虚的方法
static的方法,以声明的类型为准,与实例类型无关
private方法子类看不见,也不会被虚化
final方法子类不能覆盖,不存在虚化问题
class example
{static void doStuff( Shape s ){s.draw();}public static void main( String [] args ){Circle c = new Circle();Triangle t = new Triangle();Line l = new Line();doStuff(c);doStuff(t);doStuff(l);Shape s = new Circle();doStuff(s);s.draw();Circle c2 = new Circle();//可见static是非虚的,只跟声明的类型有关c2.draw();}
}
class Shape
{static void draw(){ System.out.println("Shape Drawing"); }
}
class Circle extends Shape
{static void draw(){ System.out.println("Draw Circle"); }
}class Triangle extends Shape
{static void draw(){ System.out.println("Draw Three Lines"); }
}class Line extends Shape
{static void draw(){ System.out.println("Draw Line"); }
}//输出:
//Shape Drawing
//Shape Drawing
//Shape Drawing
//Shape Drawing
//Shape Drawing
//Draw Circle
3 . 对象构造与初始化
3.1 构造方法(constructor)
对象都有构造方法
如果没有,编译器加一个default构造方法 (默认构造方法什么都不干)
抽象类也有构造方法,任何一个对象都需要构造
调用本类或父类的构造方法
this调用本类的其他构造方法。
super调用直接父类的构造方法
this或super要放在第一条语句,且只能够有一条
如果没有this及super,则编译器自动加上super(),即调用直接父类 不带参数的构造方法
因为必须令所有父类的构造方法都得到调用,否则整个对象的构建就 可能不正确。
例子:
class example2
{public static void main(String[] args){ Person p = new Graduate();}
}class Person
{String name; int age;Person(){}Person( String name, int age ){this.name=name; this.age=age; System.out.println("In Person(String,int)");}
}class Student extends Person
{String school;Student(){this( null, 0, null );System.out.println("In Student()");}Student( String name, int age, String school ){super( name, age );this.school = school;System.out.println("In Student(String,int,String)");}
}class Graduate extends Student
{String teacher="";Graduate(){//super();System.out.println("In Graduate()");}
}//输出结果:
//In Person(String,int)
//In Student(String,int,String)
//In Student()
//In Graduate()
上面代码可见虽然只写了一个new,但是它是一直调用父类的构造方法,直到object
class A { A(int a){} } class B extends A { B(String s){} //编译不能够通过.}
编译器会自动调用B(String s){ super();} 他会调用不带参数的构造方法,但是父类没有不带参数的,所以出错.
解决方法:
在B的构造方法中,加入super(3);
在A中加入一个不带参数的构造方法,A(){}
去掉A中全部的构造方法,则编译器会自动加入一个不带参数的构造方法,称为默认的构造方法
3.2 创建对象时初始化
p = new Person(){{ age=18; name=“李明”; }};
这样就 不 用 写 p. name,p.age,方便一点
这样可以针对没有相应构造函数,但又要赋值
注意双括号
3.3 实例初始化与静态初始化
实例初始化(Instance Initializers)
在类中直接写
{ 语句…. }
实例初始化,先于构造方法{}中的语句执行(先于this或super之外的那些语句)
尽量少用这种{},有点怪怪的感觉。。
静态初始化(Static Initializers)
static { 语句…. } 只是与类有关的而不是跟实例有关的
静态初始化,在第一次使用这个类时要执行,
但其执行的具体时机是不确定的
但是可以肯定的是:总是先于实例的初始化
例子:
class InitialTest
{public static void main(String[] args) {new InitialTest2(6);}int n=10; //step2{n++;System.out.println("InitialTest..."+n);}static int x;static {x++;System.out.println("static..." +x);}}class examp extends InitialTest{examp(int a){ this.a=a; System.out.println("this.a=" + a );}int a;//实例初始化的语句{System.out.println("InitialTest2..."+this.a);}static//这个初始化要先于实例的初始化{x++;System.out.println("static2..." +x);}
}
//输出:
//static...1
//static...1
//static2...2
//InitialTest...11
//InitialTest2...0
//this.a=6
3.4 构造方法的执行过程
构造方法的执行过程遵照以下步骤:
调用本类或父类的构造方法,直至最高一层(Object)
按照声明顺序执行字段的初始化赋值
执行构造函数中的其它各语句(不包括this或super)
简单地说:
先父类构造,再本类成员赋值,最后执行构造方法中的语句。
例子:
class JavaPConstructor
{int a=2000;JavaPConstructor(){this.a=3000;}
}
下面我们看一下反汇编后的结果
Compiled from "JavaPConstructor.java"
class text3.JavaPConstructor {int a;text3.JavaPConstructor();Code:0: aload_01: invokespecial #10 // Method java/lang/Object."<init>":()V4: aload_05: sipush 20008: putfield #12 // Field a:I11: aload_012: sipush 300015: putfield #12 // Field a:I18: return
}
可见,它先调用了object方法,虽然没有写super,但实际上他会调用super。第二步把两千赋值到字段里面。(执行实例初始化及对字段的赋值)第三步才执行构造方法里面的语句
例子:(这次有super有this)
class ConstructS
{public static void main(String[] args){ Person p = new Student("李明", 18, "北大");}
}class Person
{String name="未命名"; //step 2int age=-1;Person( String name, int age ){super(); //step 1//step 3System.out.println( "开始构造Person(),此时this.name="+this.name+",this.age="+this.age );this.name=name; this.age=age; System.out.println( "Person()构造完成,此时this.name="+this.name+",this.age="+this.age );}
}class Student extends Person
{String school="未定学校"; //step2 Student( String name, int age, String school ){super( name, age ); //step 1看似一步其实包含父类三步//step 3System.out.println( "开始构造Student(),此时this.name="+this.name+",this.age="+this.age+",this.school="+this.school );this.school = school;System.out.println( "Student()构造完成,此时this.name="+this.name+",this.age="+this.age+",this.school="+this.school );}
}
//输出结果:
//开始构造Person(),此时this.name=未命名,this.age=-1
//Person()构造完成,此时this.name=李明,this.age=18
//开始构造Student(),此时this.name=李明,this.age=18,this.school=未定学校
//Student()构造完成,此时this.name=李明,this.age=18,this.school=北大
构造方法内部调用别的的方法
如果这个方法是虚方法,结果如何?
从语法上来说这是合法的,但有时会造成事实上的不合
class ConstructorInvokeVirtual
{public static void main(String[] args){ Person p = new Student("Li Ming", 18, "PKU");}
}class Person
{String name="未命名"; int age=-1;Person( String name, int age ){this.name=name; this.age=age; sayHello();}void sayHello(){System.out.println( "A Person, name: " + name + ", age: "+ age );}
}class Student extends Person
{String school="未定学校";Student( String name, int age, String school ){super( name, age );this.school = school;//赋值这是第三步才执行的}void sayHello(){//子类覆盖了父类的sayhello,父类就会一下跳到子类,但是此时还没有赋值好System.out.println( "A Student, name:" + name + ", age: "+ age + ", school: " + school );}
}
在本例中,在构造方法中调用了一个动态绑定的方法sayHello(),这时, 会使用那个方法被覆盖的定义,而这时对象尝未完全构建好,所以 School还没有赋值。
在构造方法中尽量避免调用任何方法,尽可能简单地使对象进入就绪 状态
惟一能够安全调用的是final的方法。这就不会有虚方法的问题
4 . 对象清除与垃圾回收
我们知道:new创建对象 那么如何销毁对象?
Java中是自动清除 不需要使用delete
4.1 对象的自动清除
垃圾回收(garbage collection )
对象回收是由 Java虚拟机的垃圾回收线程来完成的。
为什么系统知道对象是否为垃圾
任何对象都有一个引用计数器,当其值为0时,说明该对象可以回收
(任何对象我们要用它,它都是一个引用)(如果对象实体空间没有被任何引用所引用,那么其值为零)
引用计数示意(可见他是自动的)
String method(){ String a,b; a=new String(“hello world”); b=new String(“game over”); System.out.println(a+b+”Ok”); a=null; //hello world 没被引用a=b; //game over有两个引用return a; }//结束了,引用都没了,但是如果调用了这个函数,又有引用了
System.gc ()方法
System.gc()方法
它是System类的static方法
它可以要求系统进行垃圾回收
但它仅仅只是”建议(suggest)”
你没法强制,只是希望虚拟机有空有条件时候进行垃圾回收
finalize() 方法
Java中没有“析构方法(destructor)”
但Object的finalize() 有类似功能
系统在回收时会自动调用对象的finalize() 方法。
protected void finalize() throws Throwable{}
子类的finalize()方法
可以在子类的finalize()方法释放系统资源
**一般来说,子类的finalize()方法中应该调用父类的finalize()方法,**以保证父 类的清理工作能够正常进行。
try -with-resources
由于finalize()方法的调用时机并不确定,所以一般不用finalize()
关闭打开的文件、清除一些非内存资源等工作需要进行处理
可以使用try-with-resources语句(JDK1.7以上)
对于实现了java.lang.AutoCloseable的对象
try( Scanner scanner= new Scanner( … ) ){ 。。。。。。 }
会自动调用其close()方法,相当于,不管你try里面正常异常都会做下面的事情
finally{ Scanner.close(); }
5 . 内部类与匿名类
定义
内部类( inner class )是在其他类中的类 (其他的类中再定义类)
匿名类( anonymous class)是一种特殊的内部类,它没有类名。
内部类(Inner class)
内部类的定义
将类的定义class xxxx{…}置入一个类的内部即可
编译器生成xxxx$xxxx这样的class文件
内部类不能够与外部类同名
内部类的使用
在封装它的类的内部使用内部类,与普通类的使用方式相同
在其他地方使用
类名前要冠以外部类的名字。
在用new创建内部类实例时,也要在 new前面冠以对象变量。
外部对象名.new 内部类名(参数)
例子:
class TestInnerClass{public static void main( String[] args ){Parcel p = new Parcel();p.testShip();Parcel.Contents c = p.new Contents(33);//前面要加上外部类的类名Parcel.Destination d = p.new Destination( "Hawii" );p.setProperty( c, d );p.ship();}
}class Parcel {private Contents c;private Destination d;class Contents {private int i;Contents( int i ){ this.i = i; }int value() { return i; }}class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() { return label; }}void setProperty( Contents c, Destination d ){this.c =c; this.d = d;}void ship(){System.out.println( "move "+ c.value() +" to "+ d.readLabel() );}public void testShip() {c = new Contents(22);d = new Destination("Beijing");ship();}
}
在内部类中使用外部类的成员
内部类中可以直接访问外部类的字段及方法
即使private也可以
如果内部类中有与外部类同名的字段或方法,则可以用
外部类名.this.字段及方法
(平时用this都是指当前的,但是你这个指的是外部的字段及方法)
例子:
public class TestInnerThis
{ public static void main(String args[]){A a = new A();A.B b = a.new B();b.mb(333); }
}class A
{private int s = 111;public class B {private int s = 222;public void mb(int s) {System.out.println(s); // 局部变量sSystem.out.println(this.s); // 内部类对象的属性sSystem.out.println(A.this.s); // 外层类对象属性s}}
}//333
//222
//111
内部类的修饰符
内部类与类中的字段、方法一样是外部类的成员,它的前面也可以有 访问控制符和其他修饰符。
访问控制符:public,protected,默认(没有修饰符)及private。
注:外部类只能够使用public修饰或者默认 (package)
final,abstract(表示他是不可继承的)
static 修饰符
用static修饰内部类 表明该内部类实际是一种外部类 (对象.new就不用了)
因为它与外部类的实例无关
有人认为static的类是嵌套类(nested class),不是内部类inner class
static类在使用时:
1、实例化static类时,在 new前面不需要用对象实例变量; (因为他和实例无关)
2、static类中不能访问其外部类的非static的字段及方法,既只能够访问static成员。
3、static方法中不能访问非static的域及方法,也不能够不带前缀地new 一个非 static的内部类。
例子:
class TestInnerStatic
{public static void main(String[] args) {A.B a_b = new A().new B(); // okA a = new A();A.B ab = a.new B();Outer.Inner oi = new Outer.Inner();//Outer.Inner oi2 = Outer.new Inner(); //!!!error //Outer.Inner oi3 = new Outer().new Inner(); //!!! error}
}class A
{private int x;void m(){new B();}static void sm(){//new B(); // error!!!!}class B{B(){ x=5; }}
}class Outer
{static class Inner{}
}
局部类
在一个方法中也可以定义类,这种类称为”方法中的内部类”
或者叫局部类(local class)
例子:
class TestInnerInMethod
{public static void main(String[] args) {Object obj = new Outer().makeTheInner(47);System.out.println("Hello World!" + obj.toString() );}
}class Outer
{private int size = 5;public Object makeTheInner( int localVar )//普通的方法{final int finalLocalVar = 99;class Inner {//这就是局部类public String toString() {return ( " InnerSize: " + size + //可以访问外部类的成员// " localVar: " + localVar + // Error! 不能访问内部普通的变量" finalLocalVar: " + finalLocalVar//可以访问该方法的final局部变量);}}return new Inner();}
}
//Hello World! InnerSize: 5 finalLocalVar: 99
使用局部类
1、同局部变量一样,方法中的内部类
不能够用 public,private,protected,static修饰,(不能修饰局部变量同类也不能修饰局部类) 但可以被final(不可继承)或者abstract(抽象)修饰。
2、可以访问其外部类的成员
3、不能够访问该方法的局部变量,(它是随时产生随时消失的,进到方法里就有,退出就消失了,是不可捉摸的不可访问的)除非是final局部变量
匿名类
匿名类( anonymous class)是一种特殊的内部类
它没有类名,在定义类的同时就生成该对象的一个实例
“一次性使用”的类(所以没必要给他个名字)
(一般他是扩展一个或者说要继承一个类,实现一个接口)
例子:
class TestInnerAnonymous
{public static void main(String[] args) {Object obj = new Outer().makeTheInner(47);System.out.println("Hello World!" + obj.toString() );}
}class Outer
{private int size = 5;public Object makeTheInner( int localVar ){final int finalLocalVar = 99;return new Object() {//这个类没取名字,但是总得要写,所以他写它父类的名字//或者它实现接口的名字public String toString() {return ( " InnerSize: " + size + " finalLocalVar: " + finalLocalVar);}};//实际上是方法体里面的类的简写,把new这个对象以及定义这个对象一起写}
}
//Hello World! InnerSize: 5 finalLocalVar: 99
匿名 类的使用
1、不取名字,直接用其父类或接口的名字。
也就是说,该类是父类的子类,或者实现了一个接口
编译器生成 xxxxx$1之类的名字
2、类的定义的同时就创建实例,即类的定义前面有一个new
new 类名或接口名(){……}
不使用关键词class,也不使用extends及implements。直接写父类的名字就完事
3、在构造对象时使用父类构造方法
不能够定义构造方法,因为它没有名字
如果new对象时,要带参数,则使用父类的构造方法
匿名 类的应用
用到界面的事件处理 (继承一个类)
注册一个事件侦听器
示例 AutoScore.java 中 //SymAction lSymAction = new SymAction(); //btnNew.addActionListener(lSymAction);btnNew.addActionListener(new ActionListener(){public void actionPerformed(ActionEvent event) { btnNew_ActionPerformed(event); } });
作为方法的参数
排序,给一个比较大小的接口
如 SortTest.java
Arrays.<Book>sort( books, new Comparator<Book>(){
//需要一个接口去比较这两本书
public int compare(Book b1, Book b2){ return b1.getPrice()-b2.getPrice();
}
});
6 . Lambda表达式
Lambda表达式是从Java8增加的新语法
Lambda表达式(λ expression)的基本写法
(参数)->结果
如 (String s) -> s.length()
如 x->x*x
如 () -> { System.out.println(“aaa”); }
大体上相当于其他语言的“匿名函数”或“函数指针”
在Java中它实际上是“ 匿名类的一个实例
例子:
class LambdaRunnable {public static void main(String argv[]) {Runnable doIt = new Runnable(){public void run(){ System.out.println("aaa");}};new Thread( doIt ).start();Runnable doIt2 = ()->System.out.println("bbb");//简洁new Thread( doIt2 ).start();new Thread( ()->System.out.println("ccc") ).start();//作为线程的一个参数更简洁}
}
可以看出
Lambda表达式是接口或者说是接口函数的简写
其基本写法是参数->结果
这里,参数是()或1个参数或 (多个参数)
结果是指 表达式 或 语句 或 {语句}
例子:
@FunctionalInterface
interface Fun { double fun( double x );}public class LambdaIntegral
{public static void main(String[] args){double d = Integral( new Fun(){public double fun(double x){ return Math.sin(x); }}, 0, Math.PI, 1e-5 );//简写d = Integral( x->Math.sin(x),0, Math.PI, 1e-5 );System.out.println( d );d = Integral( x->x*x, 0, 1, 1e-5 );System.out.println( d );}static double Integral(Fun f, double a, double b, double eps)// 积分计算{int n,k;double fa,fb,h,t1,p,s,x,t=0;fa=f.fun(a); fb=f.fun(b);n=1; h=b-a;t1=h*(fa+fb)/2.0;p=Double.MAX_VALUE;while (p>=eps){ s=0.0;for (k=0;k<=n-1;k++){ x=a+(k+0.5)*h;s=s+f.fun(x);}t=(t1+h*s)/2.0;p=Math.abs(t1-t);t1=t; n=n+n; h=h/2.0;}return t;}}
Lambda大大地简化了书写
在线程的例子中
new Thread( ()->{ … } ).start();
在积分的例子中
d = Integral( x->Math.sin(x), 0, 1, EPS );
d = Integral( x->x*x, 0, 1, EPS );
d = Integral( x->1, 0, 1, EPS );
在按钮事件处理中
btn.addActionListener( e->{ … } ) );
能写成 Lambda的接口的条件
由于Lambda只能表示一个函数,所以
能写成Lambda的接口要求包含且最多只能有一个抽象函数
这样的接口可以(但不强求)用注记
@FunctionalInterface 来表示。称为函数式接口
如
@FunctionalInterface
interface Fun { double fun( double x );}
再举一例:排序
Comparator<Person> compareAge = (p1, p2) -> p1.age-p2.age;
Arrays.sort(people, compareAge);
Arrays.sort(people, (p1, p2) -> p1.age-p2.age);
Arrays.sort(people, (p1, p2) -> (int)(p1.score-p2.score));
Arrays.sort(people, (p1, p2) -> p1.name.compareTo(p2.name));
Arrays.sort(people, (p1, p2) -> -p1.name.compareTo(p2.name));
Lambda表达式,不仅仅是简写了代码,
更重要的是:
它将代码也当成数据来处理
函数式编程
7 . 装箱、枚举、注解
基本类型的包装类
基本类型的包装类
它将基本类型(primitive type) 包装成Object(引用类型)(因为基本类型没法当对象用)
如int->Interger
共8类:
Boolean, Byte, Short, Character, Integer, Long, Float, Double
Integer I = new Integer(10);
装箱与拆箱
装箱(Boxing) Integer I = 10;
拆箱(Unboxing) int i = I;
实际译为
Integer I= Integer.valueOf(10);
int i = I.intValue();
主要方便用于集合中,如: Object [] ary = { 1, “aaa”};
枚举
枚举(enum)是一种特殊的class类型
在简单的情况下,用法与其他语言的enum相似
enum Light { Red, Yellow, Green };
Light light = Light.Red;
但实际上,它生成了 class Light extends java.lang.Enum
自定义枚举
可以在enum定义体中,添加字段、方法、构造方法
enum Direction { EAST("东",1), SOUTH("南",2), WEST("西",3), NORTH("北",4); private Direction(String desc, int num){ this.desc=desc; this.num=num; } private String desc; private int num; public String getDesc(){ return desc; } public int getNum(){ return num; } }
注解
注解(annotation)
又称为注记、标记、标注、注释(不同于comments)
是在各种语法要素上加上附加信息,以供编译器或其他程序使用
所有的注解都是 java.lang.annotation. Annotation 的子类
常用的注解,如
@Override 表示覆盖父类的方法
@Deprecated 表示过时的方法
@SuppressWarnings 表示让编译器不产生警告
自定义注解,比较复杂
public @interface Author {
String name();
}
8 . 没有指针的Java语言
引用(reference)实质就是指针(pointer)
但是它是受控的、安全的
比如
会检查空指引
没有指针运算 *(p+5)
不能访问没有引用到的内存
自动回收垃圾
C 语言指针在Java中的体现
(1)传地址 ->对象
引用类型,引用本身就相当于指针
可以用来修改对象的属性、调用对象的方法
基本类型:没用对应的
如交换两个整数
void swap(int x, int y){ int t=x; x=y; y=t; }
int a=8, b=9; swap(a.b);
一种变通的办法,传出一个有两个分量x,y的对象
(2)指针运算 -> 数组
*(p+5) 则可以用 args[5]
(3)函数指针 -> 接口、Lambda表达式
例:求积分,线程 、回调函数、事件处理
(4)指向结点的指针-> 对象的引用
class Node {
Object data;
Node next;
}
(5)使用JNI
Java Native Interface(JNI)
它允许Java代码和其他语言写的代码进行交互
java中相等还是不等
基本 类型的相等
数值类型:转换后比较
浮点数,最好不直接用==
Double.NAN==Double.NAN 结果为false
请参见JDK的API文档
boolean型无法与int相比较
装箱 对象是否相等
Integer i = new Integer(10); Integer j = new Integer(10); System.out.println(i==j); //false,因为对象是两个Integer m = 10; Integer n = 10; System.out.println(m==n); //true,因为对象有缓存Integer p = 200; Integer q = 200; System.out.println(p==q); //false,因为对象是两个,不能超过-128到+127
注意缓存
If the value p being boxed is true, false, a byte, or a char in the range \u0000 to \u007f, or an int or short number between -128 and 127 (inclusive), then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.
枚举、引用对象是否相等
枚举类型
内部进行了惟一实例化,所以可以直接判断
引用对象
是直接看两个引用是否一样
如果要判断内容是否一样,则要重写equals方法
如果重写equals方法,则最好重写 hashCode()方法
String 对象的特殊性
String对象
判断相等,一定不要用==,要用equals
但是字符串常量( String literal)及字符串常量会进行内部化(interned), 相同的字符串常量是 = =的
例子:
String hello = "Hello", lo = "lo"; System.out.println( hello == "Hello"); //true System.out.println( Other.hello == hello ); //true
System.out.println( hello == ("Hel"+"lo") ); //true
System.out.println( hello == ("Hel"+lo) ); //false
System.out.println( hello == new String("Hello")); //false
System.out.println( hello == ("Hel"+lo).intern()); //true