面向对象
- 一、概念理解
- 二、类和对象的使用说明
- 三、类中属性的使用
- 四、类中方法的声明与使用
- 五、类实例化的举例说明
- 六、方法的重载(overload)
- 七、值传递机制
- 八、可变个数形参的方法
- 九、面向对象的特征一:封装与隐藏
- 十、类的结构之三:构造器(或构造函数、constructor)的使用
- 十一、this、package、import关键字的使用关键字的使用
- 十二、面向对象的特征之二:继承性
- 十三、面向对象特征之三:多态性
- 十四、Object类
- 十四、Java中的JUnit单元测试
- 十五、包装类的使用
- 十六、main()方法、static关键字、单例设计模式
- 十七、类的成员之四(代码块)、final关键字、abstract关键字
- 十八、接口、内部类、JDK8新特性
一、概念理解
1.Java面向对象学习的三条主线
- Java类及类的成员:属性、方法、构造器;代码块、内部类(前三个很重要)
- 面向对象的三大特征:封装性、继承性、多态性、(抽象性)
- 其他关键字:this、super、static、final、abstract、interface、package、import
2.“人把大象装进冰箱”(面向过程和面向对象)
- 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
(1)把冰箱门打开
(2)抬起大象,塞进冰箱
(3)把冰箱门关闭 - 面向对象:将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
人{
打开(冰箱){
冰箱.开开();
冰箱.关闭();
}
抬起(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.关闭();
}
}
大象{
进入(冰箱){
}
}
冰箱{
开开(){}
关闭(){}
}
3.面向对象的两个要素
- 类:类是对一类事物的描述,是抽象的、概念上的定义
- 对象:对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
面向对象程序设计的重点是类的设计
类的设计,其实就是类的成员的设计
二、类和对象的使用说明
1.设计类,其实就是设计类的成员
- 属性 = 成员变量 = filed = 域、字段
- 方法 = 成员方法 = 函数 = method
- 创建类的对象 = 类的实例化 = 实例化类
2.类和对象的使用(面向对象思想落地的实现)
1.创建类,设计类的成员
2.创建类的对象
3.通过"对象.属性"或"对象.方法"调用对象的结构
3.如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的
),意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值
4.对象的内存解析
public class PersonTest {
public static void main(String[] args){
//2.创建Person类的对象
Person p1 = new Person();
//Scanner scanner = new Scanner(System.in);
//3.调用对象的结构:属性、方法
//调用属性:"对象.属性"
p1.name = "Tom";
p1.isMale = true;
System.out.println(p1.name);
//调用方法:"对象.方法"
p1.eat();
p1.sleep();
p1.talk("chinese");
//************************************
Person p2 = new Person();
System.out.println(p2.name); //null
System.out.println(p2.isMale); //false
//将p1变量保存的对象地址值赋给p3,导致p1和p3指向了堆空间中的同一个对象实体。
Person p3 = p1;
p3.age = 10;
System.out.println(p3.name); //Tom
System.out.println(p1.age); //10
}
}
//1.创建类,设计类的成员
class Person{
String name;
int age = 1;
boolean isMale;
//方法
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人可以睡觉");
}
public void talk(String language){
System.out.println("人可以说话,使用的是 " + language);
}
}
三、类中属性的使用
属性(成员变量) vs 局部变量
1.相同点
1.1 定义变量的格式:变量类型 变量名 = 变量值
1.2 先声明,后使用
1.3 变量都有其对应的作用域
2.不同点
2.1 在类中声明的位置不同
属性:直接定义在类的一对{}内
局部变量:声明在方法体内、方法形参、代码块内、构造器形参、构造器内部的变量
2.2 关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符。常用的权限修饰符:private、public、缺省、protected ---->封装性;
局部变量:不可以使用权限修饰符
2.3 默认初始化值的情况
属性:类的属性,根据其类型,都有默认初始化值。
整型(byte、short、int、long):0
浮点型(float、double):0.0
字符型(char):0(或'\u0000')
,非字符’0’
布尔型(boolean):false
引用数据类型(类、数组、接口):null
局部变量:没有默认初始化值
。意味着:我们在调用局部变量之前,一定要显式赋值
特别地,形参在调用时,我们赋值即可。
2.4 在内存中加载的位置:
属性:加载到堆空间中
(非static)
局部变量:加载到栈空间
public class UserTest {
public static void main(String[] args){
User u1 = new User();
System.out.println(u1.name);
System.out.println(u1.age);
System.out.println(u1.isMale);
u1.talk("日语");
u1.eat();
}
}
class User{
//属性(或成员变量)
String name;
int age;
boolean isMale;
public void talk(String language){ //language:形参,也是局部变量
System.out.println("我们使用" + language + "进行交流");
}
public void eat(){
String food = "烙饼"; //局部变量
System.out.println("北方人喜欢吃" + food);
}
}
四、类中方法的声明与使用
1.方法的定义
方法:描述类应该具有的功能
比如:Math类:sqrt()\random()…; Scanner类:nextXxx()…; Arrays类:sort()\binarySearch()\toString()\equals()…
举例:
public void eat(){}
public void sleep(int hour){}
public String getName(){}
public String getNation(String nation){}
2.方法的声明
权限修饰符 返回值类型 方法名(形参列表){
方法体
}
注意:还可用static、final、abstract来修饰方法
3.说明
- 3.1 关于权限修饰符:默认方法的权限修饰符先都使用public
Java规定的4种权限修饰符:private、public、缺省、protected —>封装性再细讲 - 3.2 返回值类型:有返回值 vs 没有返回值
3.2.1 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,在方法中, 需要使用return 关键字来返回指定类型的变量或常量。“return 数据”。
如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中, 就不再使用return。但是,如果使用的话,只能"return;"表示结束此方法的意思
。
3.2.2 我们定义方法该不该有返回值?
(1)题目要求
(2)凭经验,具体问题具体分析 - 3.3 方法名:属于标识符,遵循标识符的规则和规范,“见名起意”
- 3.4 形参列表:方法可以声明0个,1个,或多个形参
3.4.1 格式:数据类型1 形参1,数据类型2 形参2,…
3.4.2 我们定义方法时,该不该定义形参?
(1)题目要求
(2)凭经验,具体问题具体分析 - 3.5 方法体:方法功能的体现。
4.return关键字的使用
4.1 使用范围:使用在方法体中
4.2 作用:
(1)结束方法
(2)针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据
注意:
return关键字后面不可以声明执行语句
。
5.方法的使用:可以调用当前类中的属性或方法
特殊的,方法A中调用了方法A:递归方法。
方法中不能定义另一个方法
public class CustomerTest {
public static void main(String[] args){
Customer cust1 = new Customer();
cust1.sleep(8);
}
}
//客户类
class Customer{
//属性
String name;
int age;
boolean isMale;
//方法
public void eat(){
System.out.println("客户吃饭");
return;
//return后不可以声明表达式
//System.out.println("客户吃饭");
}
public void sleep(int hour){
System.out.println("休息了" + hour + "小时");
eat();
}
public String getName(){
/*
if(age > 18){
return name;
}
else if(age > 15) return name;
else return "Tom";
*/
return name;
}
public String getNation(String nation){
String info = "我的国籍是 " + nation;
return info;
}
}
五、类实例化的举例说明
1.理解"万事万物皆对象"
1.1 在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构,Scanner,String等;文件:File; 网络资源:URL
1.2 涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时, 都体现为类、对象。
2.内存解析的说明
引用类型的变量只可能存储两类值:null 或地址值(含变量的类型)
3.匿名对象的使用
3.1.理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象
3.2.特征:匿名对象只能调用一次。
3.3 使用
public class InstanceTest {
public static void main(String[] args) {
//错误的,局部变量没有默认初始化值,要使用必须进行显式赋值
//int a;
//System.out.println(a);
int a = 10; //正确的
System.out.println(a);
Phone p1 = new Phone();
//p1 = null;
System.out.println(p1); //地址值
p1.sendEmail();
p1.playGame();
//匿名对象
// new Phone().sendEmail();
// new Phone().playGame();
new Phone().price = 1999;
new Phone().showPrice(); //0.0
PhoneMall pm = new PhoneMall();
pm.mall(new Phone());
}
}
class PhoneMall{
public void mall(Phone p){
p.sendEmail();
p.playGame();
}
}
class Phone{
double price; //价格
public void sendEmail(){
System.out.println("发送邮件");
}
public void playGame(){
System.out.println("玩游戏");
}
public void showPrice(){
System.out.println("这款手机的价格为:" + price);
}
}
补充:关于打印函数
println
int型和char型调用的是不同的println函数
public static void main(String[] args) {
//int型和char型调用的是不同的println函数
int[] arr = new int[]{1,2,3};
System.out.println(arr);//地址值
char[] arr1 = new char[]{'a','b','c'};
System.out.println(arr1); //abc
}
六、方法的重载(overload)
1.定义
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
。
“两同一不同”:同一个类、相同方法名;
参数列表不同:参数个数不同,参数类型不同
2.举例
Arrays类中重载的sort()/binarySearch()
3.判断是否是重载
跟方法的权限修饰符、返回值类型
、形参变量名、方法体都没有关系!
通过对象调用方法时,如何确定某一个指定的方法:方法名—>参数列表
public class OverLoadTest {
public static void main(String[] args) {
OverLoadTest test = new OverLoadTest();
test.getSum(1, 2);
}
//如下的4个方法构成了重载
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double i,double j){
System.out.println("2");
}
public void getSum(String s,int i){
System.out.println("3");
}
public void getSum(int i,String s){
System.out.println("4");
}
// public int getSum(int i,int j){
// return 0;
// }
// public void getSum(int m,int n){
//
// }
// private void getSum(int i,int j){
//
// }
}
七、值传递机制
关于变量的赋值
- 如果变量是基本数据类型,此时赋值的是变量所保存的数据值
- 如果变量是引用数据类型,此时赋值的是变量所保存的数据的地址值
1.方法的形参的传递机制:值传递
- 形参:方法定义时,声明的小括号内的参数
- 实参:方法定义时,实际传递给形参的数据
2.值传递机制
- 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
- 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值。
下面给形参传参相当于复制了一份参数传给它,不会改变原来参数的值,在C++中是通过传入引用来解决这个问题的。
public class ValueTransferTest1 {
public static void main(String[] args) {
int m = 10;
int n = 20;
System.out.println("m = " + m + "," + "n = " + n);
//交换两个变量的值的操作
// int temp = m;
// m = n;
// n = temp;
ValueTransferTest1 test1 = new ValueTransferTest1();
test1.swap(m,n); //没换成,m还是10,n还是20
System.out.println("m = " + m + "," + "n = " + n);
}
public void swap(int m,int n){
int temp = m;
m = n;
n = temp;
}
}
上面代码中未成功交换两个参数的问题通过传入引用数据类型来解决
public class ValueTransferTest2 {
public static void main(String[] args) {
Data data = new Data();
data.m = 10;
data.n = 20;
System.out.println("m = " + data.m + "," + "n = " + data.n);
//交换m和n的值
// int temp = data.m;
// data.m = data.n;
// data.n = temp;
ValueTransferTest2 test2 = new ValueTransferTest2();
test2.swap(data);
System.out.println("m = " + data.m + "," + "n = " + data.n)
}
public void swap(Data data){
int temp = data.m;
data.m = data.n;
data.n = temp;
}
}
class Data{
int m;
int n;
}
八、可变个数形参的方法
1.jdk 5.0新增的内容
2.具体使用
2.1 可变个数形参的格式:数据类型...变量名
2.2 当调用可变个数形参的方法时,传入的参数个数是:0个,1个,2个,…
2.3 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
2.4 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载
。换句话说,二者不能共存。
2.5 可变个数形参在方法的形参中,必须声明在末尾
2.6 可变个数形参在方法的形参中,最多只能声明一个可变形参
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show("AA","BB","CC");
//还可以像下面这种方式去调
test.show(new String[]{"AA","BB","CC"});
}
public void show(int i){
}
public void show(String s){
}
public void show(String ... strs){ //可变个数形参
for(int i = 0; i < strs.length;i++){
System.out.println(strs[i]);
}
}
//与上面的可变个数形参无法共存
// public void show(String[] strs){
//
// }
// The variable argument type String of the method show must be the last parameter
//public void show(String ...strs,int i,){ //错误的
public void show(int i,String ... strs){ //正确的
}
}
九、面向对象的特征一:封装与隐藏
问题的引入:
当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性
进行赋值。这里赋值操作要受到属性的数据类型和存储范围的制约。除此之外,
没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。 (比如setLegs()),同时,我们需要避免用户再使用"对象.属性"的方式对属性进行
赋值。则需要将属性声明为私有的(private)
-------->此时,针对于属性就体现了封装性。
1.封装性的体现
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getxxx)
和设置(setxxx)。封装性的体现,需要权限修饰符来配合。
- java规定的4种权限(从小到大排列):
private、缺省、protected、public
- 4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类。(注意没有代码块)
- 具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类。
修饰类的话,只能使用:缺省、public
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
拓展:封装性的体现:(1)如上; (2)不对外暴露的私有的方法; (3)单例模式…
出了某个类所属的包之后,私有的结构、缺省声明的结构就不可以调用了
2.递归方法的使用(了解)
- 递归方法:一个方法体内调用它自身
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
public class RecursionTest {
public static void main(String[] args) {
//例1:计算1-100之间所有自然数的和
//方式1:
int sum = 0;
for(int i = 1; i <= 100; i++){
sum += i;
}
System.out.println(sum);
//方式2:
RecursionTest test = new RecursionTest();
int sum1 = test.getSum(100);
System.out.println(sum1);
//例2:计算1-n之间所有自然数的乘积:n!
//例3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大于0的整数,求f(10)的值。
int value = test.f(10);
System.out.println(value);
//例4:斐波拉契数列
test.getfa(5);
}
//例1:计算1-100之间所有自然数的和
public int getSum(int n){
if(n == 1){
return 1;
}
else{
return n + getSum(n-1);
}
}
//例2:计算1-100之间所有自然数的乘积
public int getSum1(int n){
if(n == 1){
return 1;
}
else{
return n * getSum(n-1);
}
}
//例3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大于0的整数,求f(10)的值。
public int f(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
return 2*f(n-1) + f(n-2);
}
}
//斐波拉契数列
// 输入一个数据n,计算斐波那契数列(Fibonacci)的第n个值
// 1 1 2 3 5 8 13 21 34 55
// 规律:一个数等于前两个数之和
// 要求:计算斐波那契数列(Fibonacci)的第n个值,并将整个数列打印出来
public int getfa(int n){
int res = 0;
if(n == 1){
res = 1;
}else if(n == 2){
res = 1;
}else{
res = getfa(n-2) + getfa(n-1);
}
System.out.print(res + " ");
return res;
}
}
十、类的结构之三:构造器(或构造函数、constructor)的使用
1.构造器的作用
创建对象
- 初始化对象的属性(信息)
2.说明
(1)如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
(2)定义构造器的格式:权限修饰符 类名(形参列表){ }
(3)一个类中定义的多个构造器,彼此构成重载
(4)一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器。如果需要这样的一个无参构造器,则需要自己显式的给出来。 (默认的空参构造器的类型和类的类型一致,即为缺省或者public)
(5)一个类中,至少会有一个构造器。
public class PersonTest {
public static void main(String[] args){
//创建类的对象:new + 构造器
Person p = new Person();
p.eat();
Person p1 = new Person("Tom");
System.out.println(p1.name);
}
}
class Person{
//属性
String name;
int age;
//构造器
public Person(){
System.out.println("Person()...");
}
public Person(String n){
name = n;
}
public Person(String n,int a){
name = n;
age = a;
}
//方法
public void eat(){
System.out.println("吃饭");
}
}
3.属性赋值的先后顺序
(1)默认初始化值;
(2)显式初始化;
(3)构造器中初始化
(4)通过"对象.方法"或 "对象.属性"的方式赋值
以上操作的先后顺序:(1) ---- (2) ---- (3) ----(4)
十一、this、package、import关键字的使用关键字的使用
1.this关键字
可以用来修饰
属性、方法,调用
构造器
(1)this修饰属性和方法
this理解为:当前对象(在方法内部使用)或当前正在创建的对象(在构造器内部使用)
1.1在类的方法中,我们可以使用"this.属性" 或"this.方法"
的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时
,我们必须显式的使用"this.变量"的方式,表面此变量是属性,而非形参。
1.2 在类的构造器中
,我们可以使用"this.属性"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表面此变量是属性,而非形参。
(2)this调用构造器
2.1我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器(注意是其他的构造器,不能自己调自己
)
2.2构造器中不能通过"this(形参列表)"方式调用自己
2.3如果一个类中有n个构造器,则最多有n-1个构造器中使用了"this(形参列表)"
2.4规定:"this(形参列表)"必须声明在构造器的首行
。
2.5构造器内部,最多只能声明一个"this(形参列表)
",用来调用其他的构造器
public class PersonTest {
public static void main(String[] args) {
Person p = new Person();
p.setAge(1);
System.out.println(p.getAge());
p.eat();
System.out.println();
Person p1 = new Person("Jerry",12);
System.out.println(p1.getAge());
}
}
class Person{
private String name;
private int age;
public Person(){
System.out.println("*******");
}
public Person(int age){
this();
System.out.println("$$$");
this.age = age;
}
public Person(String name){
this.name = name;
}
public Person(String name,int age){
this(age);
this.age = age;
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void eat(){
System.out.println("人吃饭");
this.study();
//或者下面这样,可以省略this
study();
}
public void study(){
System.out.println("人学习");
}
}
2.package关键字
(1)为了更好实现对项目中类的管理,提供包的管理。
(2)使用package声明类或接口所属的包,声明在源文件的首行
。
(3)包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”。
(4)每"."一次,就代表一层文件目录。
补充:同一个包下,不能命名同名的接口、类。不同的包下,可以。
3.import关键字(导入)
(1)在源文件中显式的使用import结构导入指定包下的类、接口
(2)声明在包的声明和类的声明之间
(3)如果需要导入多个结构,则并列写出即可
(4)可以使用"xxx.✳
"的方式,表示可以导入xxx包下的所有结构
*
(5)如果使用的类或接口是java.lang包下或本包下
定义的,则可以省略import
(7)如果在源文件中,使用了不同包下同名的类,则必须至少有一个类需要以全类名
的方式显示。如下的两个对象:
Account acct = new Account(1000);
com.atguigu.exer2.Account acct1 = new com.atguigu.exer2.Account(1000,2000,0.0123);
(8)使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式的导入。
(9)import static:导入指定类或接口中的静态结构:属性或方法。
十二、面向对象的特征之二:继承性
1.继承性的好处
(1)减少了代码的冗余,提高了代码的复用性
(2)便于功能的扩展
(3)为之后多态性的使用,提供了前提
2.继承性的格式:class A extends B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
2.1体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有属性和方法
。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
2.2 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的扩展。子类和父类的关系,不同于子集和集合的关系。
extends:延展、扩展
3.java中关于继承性的规定
一个父类可以被多个子类继承
- java中类的单继承性:
一个类只能有一个父类
- 子父类是相对的概念
- 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
- 子类继承父类以后,就获取了
直接父类以及所有间接父类
中所有声明的属性和方法
4.补充
- 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。意味着,
所有的java类具有java.lang.Object类声明的功能
。
5.方法的重写(override/overwrite)
(1)重写:子类继承父类以后,可以对父类中同名同参数的
方法,进行覆盖操作。
(2)应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名参数的方法时,实际执行的是子类重写父类的方法。
(3)重写的规定
- 方法的声明:
权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
//方法体
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
- 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
- 子类重写的方法的
权限修饰符不小于父类中
的叫被重写的方法的权限修饰符
特殊情况:子类不能重写父类中声明为private权限的方法
返回值类型
:
父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同的基本数据类型。
- 子类重写的方法
抛出的异常类型不大于父类被重写的方法抛出的异常类型
(具体放到异常处理时候讲) - 子类和父类中的同名同参数的方法要么都声明为
非static的(考虑重写
),要么都声明为static的(不是重写)
。
面试题:区分方法的重载与重写
6.子类对象实例化的全过程
(1)从结果上来看:(继承性)
- 子类继承父类以后,就获取了父类中声明的属性或方法。
- 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
(2)从过程上来看:
- 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的构造器,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
- 明确:虽然创建子类对象时,调用了父类的构造器,
但自始至终就创建过一个对象
,即为new的子类对象。
7.super关键字的使用
(1)super理解为:父类的
(2)super可以用来调用:属性、方法、构造器
(3)super的使用之调用属性和方法
- 3.1 我们可以在子类的方法或构造器中,通过使用"
super.属性"或"super.方法
的方式显式的调用 - 3.2 特殊情况,
当子类和父类中定义了同名的属性时
,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"
的方式,表明调用的是父类中声明的
属性。 - 3.3 特殊情况,当子类重写了父类中的方法以后,我们想在子类中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
(4)super的使用之调用构造器
- 4.1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- 4.2 "
super(形参列表)"的使用,必须声明在子类构造器的首行
! - 4.3 我们在类的构造器中,
针对于"super(形参列表)"或"this(形参列表)"只能二选一
,不能同时出现 - 4.4 在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,
则默认调用的是父类中空参的构造器:super()
- 4.5 在类的多个构造器中,至少有一个构造器中使用了"super(形参列表)",调用父类中的构造器
十三、面向对象特征之三:多态性
1.理解多态性:可以理解为一个事物的多种形态
2.何为多态性:
- 对象的多态性:
父类的引用指向子类的对象
(或子类的对象赋给父类的引用)
3.多态的使用:虚拟方法调用
有了对象的多态性以后,我们在编译期只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法
总结:编译看左边,运行看右边
4.多态性的使用前提:
(1)类的继承关系 (2)方法的重写
5.对象的多态性:只适用于方法,不适用于属性(编译和运行都看左边)
instanceof关键字的使用
1.父类的引用指向子类的对象,Person p1 = new Man();p1不能调用子类所特有的方法、属性;因为编译时p1是父类类型。
2.有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
3.如何才能调用子类特有的属性和方法?-------> 向下转型:使用强制类型转换符
。
Man m1 = (Man)p2;
m1.earnMoney();
m1.isSmoking = true;
使用强转时,可能出现ClassCastException的异常。如下这种情况:
Object o = new Date();
String str1 = (String)o;
4.instanceof关键字的使用
- a instanceof A:
判断对象a是否是类A的实例
。如果是,返回true;如果不是,返回false - 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不向下转型。如果a instanceof A返回true,a instanceof B也返回true。则
类B是类A的父类。
十四、Object类
完整名称:java.lang.Object类
1.Object类是所有Java类的根父类
2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
3.Object类中的功能(属性、方法)就具有通用性
属性:无
方法:equals()
/ toString()
/ getClass() / hashCode() / clone() / finalize()
wait()、notify()、notifyAll()
4.Object类只声明了一个空参的构造器
面试题:
final、finally、finalize(对象被回收之前会调用该方法)的区别?
1.Object类的clone()的使用
两个变量,两个对象,clone完以后相当于是又新new了一个对象
public class CloneTest {
public static void main(String[] args) {
Animal a1 = new Animal("花花");
try {
Animal a2 = (Animal) a1.clone(); //相当于是又新new了一个对象
System.out.println("原始对象:" + a1.getName());
a2.setName("毛毛");
System.out.println("clone之后的对象:" + a2.getName());
System.out.println("原始对象:" + a1.getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Animal implements Cloneable{
private String name;
public Animal() {
super();
}
public Animal(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal [name=" + name + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
2.finalize()的使用
public class FinalizeTest {
public static void main(String[] args) {
Person p = new Person("Peter", 12);
System.out.println(p);
p = null;//此时对象实体就是垃圾对象,等待被回收。但时间不确定。
System.gc();//强制性释放空间,在释放空间之前会调用finalize()方法,可以重写该方法完成自己想实现的操作
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//子类重写此方法,可在释放对象前进行某些操作
@Override
protected void finalize() throws Throwable {
System.out.println("对象被释放--->" + this);
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
运行结果为
Person [name=Peter, age=12]
对象被释放--->Person [name=Peter, age=12]
3.toString()的使用
(1)当我们打印一个对象的引用时,实际上就是调用当前对象的toString()
(2)Object类中toString()的定义:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
(3)像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用对象的toString()时,返回"实体内容"信息
(4)自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
public class ToStringTest {
public static void main(String[] args) {
Customer cust1 = new Customer("Tom",21);
System.out.println(cust1.toString());//com.atguigu.java.Customer@15db9742
//--->Customer[name = Tom,age = 21]
System.out.println(cust1);//com.atguigu.java.Customer@15db9742
//--->Customer[name = Tom,age = 21]
String str = "MM";
System.out.println(str); //MM,String类重写了toString方法
//要导入java.util.Date包
Date date = new Date(4538769098L); //Date类也重写了toString()方法
System.out.println(date); //Sun Feb 22 20:46:09 CST 1970
}
}
我们可以自己手动写出所需的toString()方法,也可以直接调IDE提供的该方法(自己要会写)
//手动实现的
@override
public String toString(){
return "Customer[name = " + name + ",age = " + age + "]";
}
//自动生成的
@Override
public String toString() {
return "Customer [name=" + name + ", age=" + age + "]";
}
4.equals()的使用
面试题 :== 和 equals()的区别
(1) == (运算符)的使用
- 可以使用在基本数据类型变量和引用数据类型变量中
- 如果比较的是
基本数据类型
变量,比较两个变量保存的数据是否相等
。(不一定类型要相同);如果比较的是引用数据类型变量
,比较两个对象的地址值是否相等
,即两个引用是否指向同一个对象实体
(2) equals()方法的使用
是一个方法
,而非运算符只能使用于引用数据类型
- Object类中equals()的定义:
public boolean equals(Object obj) {
return (this == obj);
}
说明:
Object()类中定义的equals()和==的作用是相同的
,比较两个对象的地址值是否相等,即两个引用是否指向同一个对象实体
补充:==符号使用时,必须保证符号左右两边的变量类型一致。
(要么都为引用数据类型,要么都为基本数据类型,除了boolean型变量,其不参与运算符运算)
- 像String、Date、File、包装类等都重写了Object类中的equals()方法。
重写以后
,比较的不是两个引用的地址值是否相等,而是比较两个对象的"实体内容"是否相同
- 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。
重写的原则:比较两个对象的实体内容是否相同
public class EqualsTest {
public static void main(String[] args){
//基本数据类型
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j); //true
System.out.println(i == d); //true
//布尔类型变量不参与运算符运算
boolean b = true;
//System.out.println(i == b);
//char类型的变量在底层运算时是以ASCII码的形式出现的
char c = '0';
System.out.println(0 == c); //false
char c1 = 10;
System.out.println(i == c1); //true
char c2 = 'A';
char c3 = 65;
System.out.println(c2 == c3); //true
//引用数据类型
Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
System.out.println(cust1 == cust2); //false
String str1 = new String("atguigu");
String str2 = new String("atguigu");
System.out.println(str1 == str2); //false
System.out.println("**************");
System.out.println(cust1.equals(cust2)); //false--->true
System.out.println(str1.equals(str2)); //true
Date date1 = new Date(34567823L);
Date date2 = new Date(34567823L);
System.out.println(date1.equals(date2)); //true
}
}
重写equals()方法
//自动生成的equals()
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
//重写的原则:比较两个对象的实体内容(即:name和age)是否相同
//手动生成equals()的方法
@Override
public boolean equals(Object obj) {
System.out.println("Customer equals()...");
if(this == obj){
return true;
}
if(obj instanceof Customer){
Customer cust = (Customer)obj;
if(this.age == cust.age && this.name.equals(cust.name)){
return true;
}else{
return false;
}
//或
//return this.age == cust.age && this.name.equals(cust.name);
}
return false;
}
十四、Java中的JUnit单元测试
1.步骤:此处的步骤指的是在Eclipse中的操作步骤
(1)选中当前工程 - 右键选择:build path - add libraries - JUnit4 - 下一步
(2)创建Java类,进行单元测试。此时的Java类要求:此类是public的、此类提供公共的无参的构造器
(3)此类中声明单元测试方法 <-------方法的权限是public,没有返回值,没有形参
(4)此单元测试方法最上面需要声明注解:@Test
,并在单元测试类中导入:import org.junit.Test;
(5)声明好单元测试方法以后,就可以在方法体内测试相关的代码
(6)写完代码以后,左键双击单元测试方法名,右建:run as - JUnit Test
说明:如果执行结果没有任何异常:绿条。如果执行结果出现异常:红条
package com.atguigu.java1;
import java.util.Date;
import org.junit.Test;
public class JUnitTest {
int num = 10;
@Test
public void testEquals(){
String s1 = "MM";
String s2 = "MM";
System.out.println(s1.equals(s2));
//ClassCastException的异常
// Object obj = new String("GG");
// Date date = (Date)obj;
System.out.println(num);
show();
}
public void show(){
System.out.println("show...");
}
@Test
public void testToString(){
// String str1 = "GG";
// System.out.println(str1);
String str1 = new String("GG");
System.out.println(str1.toString());
}
}
十五、包装类的使用
1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征 2.需要掌握基本数据类型、包装类、String三者之间的相互转换
public class WrapperTest {
//String类型 ---> 基本数据类型、包装类、调用包装类的parseXxx(String s)
@Test
public void test5(){
String str1 = "123";
//错误的情况:
//int num1 = (int)str1;
//Integer in1 = (Integer)str1;
//可能会出现NumberFormatException的异常
int num2 = Integer.parseInt(str1);
System.out.println(num2 + 1);
String str2 = "true";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1); //true
String str3 = "true1";
boolean b2 = Boolean.parseBoolean(str3);
System.out.println(b2); //false
System.out.println("");
Object o1;
System.out.println();
}
//基本数据类型、包装类 --->String类型:调用String重载的valueOf(Xxx xxx)
@Test
public void test4(){
int num1 = 10;
//方式一:连接运算
String str1 = num1 + "abc";
System.out.println(str1);
boolean b1 = true;
String str2 = b1 + "12";
System.out.println(str2);
//方式二:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str3 = String.valueOf(f1);
System.out.println(str3);
double d1 = new Double(13.3);
System.out.println(String.valueOf(d1));
}
/*
* JDK5.0新特性:自动装箱与自动拆箱
*/
@Test
public void test3(){
// int num1 = 10;
// //基本数据类型--->包装类的对象
// method(num1);
//自动装箱:基本数据类型 ---> 包装类
int num2 = 10;
Integer in1 = num2; //自动装箱
boolean b1 = true;
Boolean b2 = b1; //自动装箱
//自动拆箱:包装类 ---> 基本数据类型
System.out.println(in1.toString());
int num3 = in1;//自动拆箱
}
public void method(Object obj){
System.out.println(obj);
}
//包装类 ---> 基本数据类型:调用包装类的xxxValue
@Test
public void test2(){
Integer in1 = new Integer(12);
System.out.println(in1.intValue() + 1);
Float f1 = new Float(12.3);
float f2 = f1.floatValue();
System.out.println(f2 + 1);
Boolean b1 = new Boolean(true);
boolean b2 = b1.booleanValue();
//System.out.println(b2 + 1); //boolean类型的变量不参与加减乘除运算
System.out.println(b2);
Character c1 = new Character('1'); //'10'不能写,也不能直接写1
char c2 = c1.charValue();
System.out.println(c2); //1
}
//基本数据类型 --->包装类:调用包装类的构造器
@Test
public void test1(){
int num1 = 10;
//System.out.println(num1.toString());
Integer in1 = new Integer(num1);
System.out.println(in1.toString());
Integer in2 = new Integer("123");
System.out.println(in2.toString());
//报异常,
// Integer in3 = new Integer("123abc");
// System.out.println(in3.toString());
Float f1 = new Float(12.3);
Float f2 = new Float(12.3f);
Float f3 = new Float("12.3");
System.out.println(f1);
System.out.println(f3);
Boolean b1 = new Boolean("TrUe");
Boolean b2 = new Boolean(true);
System.out.println(b1); //true
Boolean b3 = new Boolean("true123");
System.out.println(b3);//false
Order order = new Order();
System.out.println(order.isMale); //false
System.out.println(order.isFemale); //null
}
}
class Order{
boolean isMale;
Boolean isFemale;
}
十六、main()方法、static关键字、单例设计模式
1、static关键字
(1)static可以用来修饰:属性、方法、内部类、代码块
(2)使用static修饰属性:静态变量 (或类变量)。属性,按是否使用static修饰,又分为:静态变量 vs 非静态变量(实例变量)
- 实例变量:我们创建了类的多个变量,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改
- 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
- static修饰属性的其他说明:
1)静态变量随着类的加载而加载
。可以通过"类.静态变量
"的方式进行调用
2)静态变量的加载要早于对象的创建。
3)由于类只会加载一次,则静态变量在内存中也只会存在一份
:存在方法区的静态域中。
1 | 类变量(静态变量) | 实例变量 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
- 静态变量举例:System.out; Math.PI(此处的PI严格来讲是叫静态常量)
(3)使用static修饰方法:静态方法
- 随着类的加载而加载,可以通过"
类.静态方法"
的方式进行调用
1 | 静态方法 | 非静态方法 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态方法中,只能调用静态的方法或属性
。非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
(4)static注意点:
在静态的方法内,不能使用this关键字、super关键字
- 关于静态属性和静态方法的使用,都从生命周期的角度去理解
(5)开发中,如何确定一个属性是否要声明为static的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为static
(6)开发中,如何确定一个方法是否要声明为static的?
- 操作静态属性的方法,通常设置为static的
- 工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections
2、main()方法
(1)main()方法作为程序的入口
(2)main()方法也是一个普通的静态方法
。
(3)main()方法也可以作为我们与控制台交互的方式。给main()方法的形参传参达到与控制台交互的目的(之前:使用Scanner)
每个源文件里面只能声明一个类型为public的类
3、单例设计模式
(1)采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
(2)如何实现? --------> 饿汉式 vs 懒汉式
(3)区分饿汉式和懒汉式
- 饿汉式:坏处:对象加载时间过长。好处:
饿汉式是线程安全的
。 - 懒汉式:好处:延迟对象的创建。目前的写法坏处:线程不安全。—>到多线程内容时,再修改
(4)饿汉式代码实现
public class SingletonTest1 {
public static void main(String[] args) { //注意main方法的形参是个string类型的数组
//Bank bank = new Bank();
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2); //true
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
(5)懒汉式代码实现
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2); //true
}
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
十七、类的成员之四(代码块)、final关键字、abstract关键字
1、代码块
(1)代码块的作用:用来初始化类、对象
(2)代码块如果有修饰的话,只能使用static
(3)分类:静态代码块 vs 非静态代码块
(4)静态代码块
- 内部可以有输出语句
随着类的加载而执行,而且只执行一次
- 作用:初始化类的信息
- 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
- 静态代码块的执行
要优先于非静态代码块的执行
- 静态代码块内
只能调用静态的属性、静态的方法
,不能调用非静态的结构
(5)非静态代码块
- 内部可以有输出语句
随着对象的创建而执
行每创建一个对象,就执行一次
非静态代码块- 作用:可以在创建对象时,对对象的属性等进行初始化
- 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
2、final关键字
(1)final可以用来修饰的结构:类、方法、变量(注意这里说的是变量
,属性只是变量的一种)
(2)final可以用来修饰一个类:此类不能被其他类所继承
。比如:String类、System类、StringBuffer类
(3)final用来修饰方法:表明此方法不可以被重写
。比如:Object类中getClass();
(4)final用来修饰变量:此时的"变量"就称为是一个常量
- final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化(每个构造器都得给final的属性赋值)
- final修饰局部变量:(位于方法体内或是方法形参位置)
- 尤其是使用final修饰形参时,表明形参是一个常量。当我们调用此方法时,给常量 形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。若用final修饰方法体内的局部变量时,该变量一旦被赋值,其值就不能再改变。
static final 用来修饰属性:全局常量
代码执行顺序总结:由父及子,静态先行(非静态代码块的执行要早已构造器的执行,因其在对象创建时被执行)
对属性可以赋值的位置:
(1)默认初始化
(2)显式初始化 / (5)在代码块中赋值
(3)构造器中初始化
(4)有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值
执行的先后顺序
:(1) - (2) / (5) - (3) - (4)
3、abstract关键字
(1)abstract可以用来修饰的结构:类、方法
(2)abstract修饰类:抽象类
此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
(3)abstract修饰方法:抽象方法
抽象方法只有方法的声明
,没有方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法
。- 子类重写了
(实现)父类中所有的抽象方法
后,此子类方可实例化 - 若子类没有重写父类中所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
(4)abstract使用上的注意点
- abstract不能用来修饰:属性、构造器等结构(构造器只能被重载,不能被重写)
abstract不能用来修饰私有方法
(子类不能重写父类中声明为private权限的方法)、静态方法
(子类和父类中的同名方法要么都声明为static的,要么都声明为非static的,而都声明为static的方法不是重写
)、final修饰的方法
(方法不能被重写)、final修饰的类
(类不能被继承)
(5)抽象类的匿名子类
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//不是抽象方法
// public void eat(){
//
// }
//抽象方法
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(){
}
public Student(String name,int age){
super(name,age);
}
//public void ea(); //不能象c++一样先声明后定义,java中声明和实现要一起写完
public void eat(){
System.out.println("学生应该多吃有营养的食物");
}
public void breath() {
System.out.println("学生应该呼吸新鲜的空气");
}
public void walk(){
}
}
public class PersonTest {
public static void main(String[] args) {
method(new Student()); //非匿名子类的匿名对象
Worker worker = new Worker();
method1(worker); //非匿名子类的非匿名的对象
method1(new Worker()); //非匿名类的匿名对象,多态
System.out.println("********************");
//创建了一匿名子类的非匿名对象:p,这个地方的p是多态
Person p = new Person(){
@Override
public void eat() {
System.out.println("好好吃饭");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃");
}
@Override
public void breath() {
System.out.println("呼");
}
});
}
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
s.eat();
s.breath();
}
}
class Worker extends Person{
@Override
public void eat() {
System.out.println("吃饭");
}
@Override
public void breath() {
System.out.println("呼吸");
}
}
(6)抽象类的应用:模板方法的设计模式,注意下面spenTime()是公用的部分,code()是对象不同,执行的代码也不同。
public class TemplateTest {
public static void main(String[] args) {
SubTemplate t = new SubTemplate();
t.spendTime();
}
}
abstract class Template{
//计算某段代码执行所需要花费的时间
public void spendTime(){
long start = System.currentTimeMillis();
code(); //不确定的部分,易变的部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code() {
for(int i = 2; i <= 1000;i++){
boolean isFlag = true;
for(int j = 2; j <= Math.sqrt(i); j++){
if(i % j == 0){
isFlag = false;
}
}
if(isFlag){
System.out.println(i);
}
}
}
}
十八、接口、内部类、JDK8新特性
1、接口
(1)接口使用interface来定义
(2)Java中,接口和类是并列的两个结构
(3)如何定义接口:定义接口中的成员
JDK7及以前:只能定义全局常量和抽象方法
(注意均为public的)- 全局常量:public static final的。但是书写时,可以省略不写,通过
接口名.变量名
的方式调用 - 抽象方法:public abstract的。也可以省略不写。
JDK8
:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
(略)
(4)接口中不能定义构造器,意味着接口不可以实例化
(5)Java开发中,接口通过类去实现(implements)的方式来调用
如果实现类覆盖了接口中的所有抽象方法
,则此实现类就可以实例化- 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
(6)Java类可以实现多个接口 —> 弥补了Java单继承性的局限性
- 格式:class AA extends BB implements CC,DD,EE(有继承又有实现的书写格式)
(7)接口与接口之间可以继承,而且可以多继承
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{
}
(8)接口的具体使用:体现多态性
(9)接口,实际上可以看做是一种规范
面试题:抽象类与接口的异同
(10)接口的应用:代理模式
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
NetWork proxyserver = new ProxyServer(server);
proxyserver.browse();
}
}
interface NetWork{
int MIN = 1;
void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器服务网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
(11)一个接口可以被多个类实现,一个类也可以实现多个接口
public class StaticProxyTest {
public static void main(String[] args) {
Star s = new Proxy(new RealStar());
s.confer();
s.signContract();
s.bookTicket();
s.sing();
s.collectMoney();
}
}
interface Star {
void confer();// 面谈
void signContract();// 签合同
void bookTicket();// 订票
void sing();// 唱歌
void collectMoney();// 收钱
}
//被代理类
class RealStar implements Star {
public void confer() {
}
public void signContract() {
}
public void bookTicket() {
}
public void sing() {
System.out.println("明星:歌唱~~~");
}
public void collectMoney() {
}
}
//代理类
class Proxy implements Star {
private Star real;
public Proxy(Star real) {
this.real = real;
}
public void confer() {
System.out.println("经纪人面谈");
}
public void signContract() {
System.out.println("经纪人签合同");
}
public void bookTicket() {
System.out.println("经纪人订票");
}
public void sing() {
real.sing();
}
public void collectMoney() {
System.out.println("经纪人收钱");
}
}
(12)接口使用上也满足多态性
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2.创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//匿名实现类的这种方式适用于只调一次
//3.创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4.创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("Mp3开始工作");
}
@Override
public void stop() {
System.out.println("Mp3开始工作");
}
});
}
}
class Computer{
public void transferData(USB usb){ //USB usb = new Flash()
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
举例:
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
//编译不通过。因为x是不明确的
//System.out.println(x);
System.out.println(super.x); //1
System.out.println(A.x); //0,接口中的x是全局常量
}
public static void main(String[] args) {
new C().pX();
}
}
2、内部类
(1)Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
(2)内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法体内、代码块内、构造器内)
(3)成员内部类:
一方面,作为外部类的成员:
调用外部类的结构;
可以被static修饰;
可以被四种不同的权限修饰;
另一方面,作为一个类:
类内可以定义属性、方法、构造器等;
可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承;
可以被abstract修饰,被其修饰以后,该类不能实例化;
(4)关注如下的3个问题
- 如何实例化成员内部类的对象
- 如何在成员内部类中区分调用外部类的结构
- 开发中局部内部类的使用 见《InnerClassTest1.java》
public class InnerClassTest {
public static void main(String[] args) {
//4.1 如何实例化成员内部类的对象
//创建Dog实例(静态的成员内部类):
//因是静态的,所以无需实例化外部类,直接用类.的方式就能调用
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类):
//Person.Bird bird = new Person.Bird(); //错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
//4.2 如何在成员内部类中区分调用外部类的结构
bird.display("黄鹂");
}
}
class Person{
String name = "小明";
int age;
public void eat(){
System.out.println("人吃饭");
}
//静态成员内部类
static class Dog{
String name;
public void show(){
System.out.println("卡拉是条狗");
//eat(); //静态成员结构不能调非静态的
}
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
int age;
public void sing(){
System.out.println("我是一只小小鸟");
Person.this.eat(); //eat()前面省略了Person.this.eat()
}
public void display(String name){
System.out.println(name); //形参
System.out.println(this.name); //该类的属性
System.out.println(Person.this.name); //外部类同名的属性
}
}
public void method1(){
//方法体内的局部内部类
class AA{
}
}
{
//代码块内的局部内部类
class BB{
}
}
public Person(){
//构造器内的局部内部类
class CC{
}
}
}
/*
* 4.3 开发中局部内部类的使用
*/
public class InnerClassTest1 {
//开发中很少见
public void method1(){
//局部内部类
class AA{
}
}
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//方式一:
// //创建一个实现了Comparable接口的类:局部内部类
// class MyComparable implements Comparable{
// @Override
// public int compareTo(Object o) {
// // TODO Auto-generated method stub
// return 0;
// }
// }
// return new MyComparable(); //此处体现了多态
// //本来是 MyComparable mycomparable = new MyComparable()
// //这样return就相当于 Comparable comparable = new MyComparable()
//方式二:匿名局部内部类的匿名对象
return new Comparable(){
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
};
}
}
public class InnerClassTest2 {
/*
* 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)
* 中的局部变量(比如:num),要求此局部变量声明为final的
*
*/
public void method(){
//局部变量
final int num = 10; //局部变量必须是final的,如果是JDK7以前的版本,final要
//显式的写出来,否则会报错,7以后可以省略,但若下面的内部类中
//修改该局部变量的值则会报错
class AA{
public void show(){
//num = 20;
System.out.println(num);
}
}
}
}
3、JDK8新特性
(1)JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。public static 返回值 函数名(){},public default 返回值 函数名() {}。public可省略
(2)接口中定义的静态方法:只能通过接口来调用。接口名.函数名
(3)通过实现类的对象,可以调用接口中的默认方法。如果类重写了接口中的默认方法,调用时调用的是重写以后的方法
(4)如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法。那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。—>类优先原则(只针对方法)
(5)如果类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。—>接口冲突。
这就需要我们必须在实现类中重写此方法
(6)如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3(); //调用自己定义的重写的方法
super.method3(); //调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}