Java
面向对象
默认值
byte,short,int,long,char----------------0
float,double----------------------------0.0
boolean---------------------------------false
引用类型---------------------------------null
1.类与对象
基本介绍
类:是一种引用数据类型
引用
数据类型 引用类型变量 指向 对象
Student zs = new Student();
OO
:面向对象
OOA
:面向对象的分析
OOD
:面向对象的设计
OOAD
:面向对象的分析与设计(OOA
与OOD
的组合)
OOP
:面向对象的编程-------------------------------你们所参与的
-
现实生活是由很多很多对象组成的,基于对象抽出类
-
对象:软件中真实存在的单个个体/东西
类:类型/类别,代表一类个体
-
类是对象的模板/模子,对象是类的具体的实例
-
类中可以包含:
- 对象的属性/特征----------------------------成员变量
- 对象的行为/动作/功能---------------------方法
-
一个类可以创建多个对象
-
高质量的代码:--------------------想拿年薪,是你们以后的目标
- 复用性好、扩展性好、维护性好、可移植性好、健壮性好、可读性好、效率好…
-
成员变量分为:实例变量和静态变量
创建类,对象,访问类中成员
-
学生类
public class Student { //成员变量中的实例变量 String name; int age; String address; //方法 void study(){ System.out.println(name+"在学习..."); } void sayHi(){ System.out.println("大家好,我叫"+name+",今年"+age+"岁了,家住"+address); } }
-
学生类的测试类
public class StudentTest { public static void main(String[] args) { //创建学生对象 Student zs = new Student(); //访问成员变量 zs.name = "zhangsan"; zs.age = 24; zs.address = "河北廊坊"; //调用方法 zs.study(); zs.sayHi(); Student ls = new Student(); ls.name = "lisi"; ls.age = 26; ls.address = "黑龙江佳木斯"; ls.study(); ls.sayHi(); //1)创建了一个学生对象 //2)给成员变量赋默认值 Student ww = new Student(); ww.study(); ww.sayHi(); } }
2.方法的重载
overload
/overloading
更方便用户的调用
-
发生在同一类中,方法名相同,参数列表不同
-
编译器在编译时会根据方法的签名自动绑定方法
-
重载的演示
public class OverloadDemo { public static void main(String[] args) { Aoo o = new Aoo(); o.show(); o.show("zhangsan"); o.show(24); o.show("zhangsan",24); o.show(24,"zhangsan"); //o.show(25.678); //编译错误,没有show(double参)的 } }
class Aoo{ void show(){} void show(String name){} void show(int age){} void show(String name,int age){} void show(int age,String name){} //int show(){ return 1; } //编译错误,重载与返回值类型无关 //void show(String address){} //编译错误,重载与参数名称无关 }
3.构造方法
-
构造函数、构造器、构建器------------------复用给成员变量赋初始值的代码
- 作用:给成员变量赋初始值
- 语法:与类同名,没有返回值类型(连
void
都没有) - 调用:在创建(
new
)对象时被自动调用 - 若自己不写构造方法,则编译器默认提供一个无参构造方法,若自己写了构造方法,则不再默认提供
- 构造方法可以重载
-
构造方法的演示
学生类
public class Student { String name; //成员变量中的实例变量 int age; String address; //构造方法----给成员变量中的实例变量赋初始值 Student(String name,int age,String address){ //局部变量 this.name = name; //ls.name="lisi" this.age = age; //ls.age=24 this.address = address; //ls.address="JMS" } void study(){ System.out.println(name+"在学习..."); } void sayHi(){ System.out.println("大家好,我叫"+name+",今年"+age+"岁了,家住"+address); } }
演示类
public class ConsDemo { public static void main(String[] args) { //Student s = new Student(); //编译错误,Student类没有无参构造 Student zs = new Student("zhangsan",25,"LF"); Student ls = new Student("lisi",24,"JMS"); zs.sayHi(); ls.sayHi(); } }
-
何时方法需要带参:
- 若方法中的数据可以写死(固定的),则不需要传参。若想把方法中的数据不能写死,则必须传参
- 窍门:当方法不知道要不要参数的时候,就统一先设计为无参的,然后去写方法体,如果方法体中的数据都能正常写出来,意味着就不需要传参,但是若写着写着不知道数据怎么写了,那就需要传参过来。
4.this
指代当前对象,哪个对象调用方法它指的就是哪个对象
只能用在方法中,方法中访问成员变量之前默认有个
this
.
-
用法:
-
this.
成员变量名--------------------------访问成员变量注意:当成员变量与局部变量同名时,若想访问成员变量,则this不能省略
-
this.
方法名()-------------------------------调用方法(一般不用,了解即可) -
this()
-----------------------------------------调用构造方法(一般不用,了解即可)
-
-
实例变量和局部变量是可以同名的,使用时默认采取就近原则,若想访问实例变量,此时
this
不能省略
5.null
表示空,没有指向对象。
注意:若引用的值为
null
,则该引用不能进行任何点操作了,若操作则发生NullPointerException
空指针异常
NullPointerException
空指针异常:
6.引用类型数组
------记住与基本类型数组的2点区别即可
-
区别1:给引用类型数组的元素赋值,需要new个对象
ObserveSubmarine[] oses = new ObserveSubmarine[3]; //创建侦察潜艇数组对象 oses[0] = new ObserveSubmarine(); //创建侦察潜艇对象 oses[1] = new ObserveSubmarine(); oses[2] = new ObserveSubmarine();
-
区别2:若想访问数组元素的数据,需要通过数组元素去打点
ObserveSubmarine[] oses = new ObserveSubmarine[3]; //创建侦察潜艇数组对象 oses[0] = new ObserveSubmarine(); //创建侦察潜艇对象 oses[1] = new ObserveSubmarine(); oses[2] = new ObserveSubmarine(); System.out.println(oses[0].x); //输出第1个侦察潜艇的x坐标 oses[1].y = 250; //修改第2个侦察潜艇的y坐标为250 oses[2].move(); //第3个侦察潜艇移动一下 for(int i=0;i<oses.length;i++){ //遍历所有侦察潜艇 System.out.println(oses[i].x+","+oses[i].y+","+oses[i].speed); //输出每个侦察潜艇的x/y/speed oses[i].move(); //每个侦察潜艇移动 }
7.内存管理(JVM
)
由
JVM
来管理的
堆
-
new
出来的对象(包括实例变量、数组的元素) -
垃圾:没有任何引用所指向的对象
垃圾回收器(
GC
)不定时到内存中清扫垃圾,回收过程是透明的(看不到的),不一定一发现垃圾就立刻回收,通过System.gc()
建议JVM
尽快调度gc
来回收 -
实例变量的生命周期:
- 创建对象时存储在堆中,对象被回收时一并被回收
-
内存泄漏:不再使用的对象没有被及时的回收,严重的泄漏会导致系统的崩溃
建议:不再使用的对象应及时将引用设置为
null
栈
- 存储正在调用的方法中的局部变量(包括方法的参数)
- 调用方法时会在栈中为该方法分配一块对应的栈帧,栈帧中存储局部变量(包括方法的参数),方法调用结束时,栈帧被清除,局部变量一并被清除
- 局部变量的生命周期:
- 调用方法时存储在栈中,方法调用结束时与栈帧一并被清除
方法区
- 存储
.class
字节码文件(包括静态变量、所有方法)- 方法只有一份,通过
this
来区分具体的调用对象
注意:基本类型变量中装的是具体的数,引用类型变量中装的是堆中对象的地址
java
中基本数据类型和引用数据类型的存放位置
-
基本数据类型存在哪里?
-
基本数据类型是放在栈中还是放在堆中,这取决于基本类型声明的位置。
-
比如
void method(){ int a=3; }
因为是在方法里定义,所以跟着方法,所以存在栈里边
class Test(){ int a=3; }
在类里边定义,所以是跟着对象的,所以存在堆中
-
-
堆是所有线程共享的内存区域,栈是每个线程独享的,如果你将一个实例变量放在栈内,那么就不存在多个线程访问同一个对象资源了,这显然是不对的,所以实例变量要在堆上创建,也不是线程安全的。
-
但是对于局部变量,是在栈上创建的,每一次方法调用创建一个帧,独享一份内存区域,其他的线程是不会访问到该线程的资源,在栈上创建也会减轻
GC
的压力,随着该方法的结束,帧出栈,相对应的内存消除,这种局部变量占用的内存自然就消失了,因此局部变量是线程安全的。 -
在方法中声明的变量
- 即使变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因。
- 在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。
(1)当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在方法栈中
(2)当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在方法的栈中,该变量所指向的对象是放在堆类存中的。
-
在类中声明的变量是成员变量
- 也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。
- 同样在类中声明的变量即可是基本类型的变量也可是引用类型的变量
(1)当声明的是基本类型的变量其变量名及其值放在堆内存中的
(2)引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中.
-
总结
局部变量 成员变量 基本数据类型 变量名和值都在方法栈 变量名和值在堆内存 引用数据类型 变量在方法栈,变量指向的对象在堆 变量名和变量名指向的对象都在堆
8.继承(派生类继承超类)
-
作用:代码复用
-
通过
extends
来实现继承,继承要符合is a(是)的关系 -
超类/父类:共有的属性和行为
派生类/子类:特有的属性和行为
-
派生类可以访问派生类的+超类的,但超类不能访问派生类的
-
一个超类可以有多个派生类,但一个派生类只能继承一个超类------------单一继承
-
具有传递性
-
java规定:构造派生类之前必须先构造超类
-
在派生类的构造方法中若没有调用超类的构造方法,则默认
super()
调用超类的无参构造方法 -
在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供
注意:
super()
调用超类构造方法,必须位于派生类构造方法的第1行
-
-
超类的意义:
- 封装共有的属性和行为-----------------------实现代码复用
- 为所有派生类提供了统一的类型-----------向上造型(实现代码复用)
9.super
(超级)
指代当前对象的超类对象
-
super
的用法:调用方式 作用 super
.成员变量名访问超类的成员变量 super
.方法名()调用超类的方法 super
()调用超类的构造方法
-
super
演示手动调用
super
class Eoo{ Eoo(int a){ } } class Foo extends Eoo{ Foo(){ super(5); }
自动调用,如下代码是默认的
//如下代码为默认的 Foo(){ super(); } }
10.向上造型
-
超类型的引用指向派生类的对象
-
能点出来什么,看引用的类型------------------这是规定,记住就可以了
何时向上造型:
- 多种角色能干的事都一样的时候,可以将多种角色统一造型到超类数组中,实现代码复用
- 例如:侦察潜艇/鱼雷潜艇/水雷潜艇都能移动,都能被炸弹打------------说明干的事都一样
- 就可以将三种潜艇统一造型到
SeaObject
超类数组中,这样仅需要一个for
即可-----代码复用
- 就可以将三种潜艇统一造型到
-
向上造型的演示
public class UpCastDemo { public static void main(String[] args) { Aoo o1 = new Aoo(); o1.a = 1; o1.show(); //o1.b = 2; //编译错误 //o1.test(); //编译错误,父类不能访问子类的 Boo o2 = new Boo(); o2.b = 1; o2.test(); o2.a = 2; //正确 o2.show(); //正确,子类可以访问父类的 Aoo o3 = new Boo(); //向上造型 o3.a = 1; o3.show(); //o3.b = 2; //编译错误 //o3.test(); //编译错误,能点出来什么,看引用的类型 } }
class Aoo{ int a; void show(){ } } class Boo extends Aoo{ int b; void test(){ } }
11.方法的重写
英文 | 翻译 |
---|---|
(override /overriding ) | 重新写,覆盖 |
-
发生在父子类中,方法名相同,参数列表相同
-
重写方法被调用时,看对象类型--------------------------这是规定,记住就可以
class 餐馆{ void 做餐(){ 做中餐 } } //1)我还是想做中餐---------------不需要重写 class Aoo extends 餐馆{ } //2)我想改做西餐-----------------需要重写 class Aoo extends 餐馆{ void 做餐(){ 做西餐 } } //3)我想在中餐基础之上加入西餐------需要重写(先super中餐,再加入西餐) class Aoo extends 餐馆{ void 做餐(){ super.做餐(); 做西餐 } }
-
遵循"两同两小一大"原则:---------------了解即可,一般都是一模一样的
-
两同:
- 方法名相同
- 参数列表相同
-
两小:
-
派生类方法的返回值类型小于或等于超类方法的
-
void和基本类型时,必须相等
-
引用类型时,小于或等于
超类大,派生类小
class Aoo{ //超类(大) void show(){} double test(){ return 0.0; } Boo sayHi(){ return null; } Aoo say(){ return null; } } class Boo extends Aoo{ //派生类(小) //int show(){ return 1; } //编译错误,void时必须相等 //int test(){ return 0; } //编译错误,基本类型时必须相等 //Boo sayHi(){ return null; } //编译错误,引用类型时必须小于或等于 Boo say(){ return null; } //正确,小于或等于 }
-
-
派生类方法抛出的异常小于或等于超类方法的异常
-
-
一大:
- 派生类方法的访问权限大于或等于超类方法的访问权限
-
12.package
和import
package
:声明包- 作用:避免类的命名冲突
- 同包中的类不能同名,但不同包中的类可以同名
- 类的全称:包名.类名
- 建议:包名所有字母都小写,包名常常有层次结构
import
:导入类- 同包中的类可以直接访问,但不能包中的类不能直接访问,若想访问:
- 先
import
导入类再访问类-----------建议 - 类的全称---------------------------------太繁琐、不建议
- 先
- 同包中的类可以直接访问,但不能包中的类不能直接访问,若想访问:
13.访问控制修饰符
-
作用:保护数据安全
-
设计规则:数据(成员变量)私有化(
private
),行为(方法)大部分公开化(public
)访问权限(英文) 翻译 作用域(什么类可以访问) public
公开的 任何类 protected
受保护的 本类、派生类、同包类 default
默认的 本类、同包类( java
不建议默认权限)private
私有的 本类 说明:
- 类的访问权限,只能是
public
或默认的。而类中成员的访问权限,如上4种都可以。 - 访问权限由低到高依次为:
public
>protected
>default
(默认的)>private
- 类的访问权限,只能是
-
访问控制修饰符的演示
package ooday04_vis; public class Coo { public int a; //任何类 protected int b; //本类、派生类、同包类 int c; //本类、同包类 private int d; //本类 void show(){ a = 1; b = 2; c = 3; d = 4; } }
演示
private
class Doo{ void show(){ Coo o = new Coo(); o.a = 1; o.b = 2; o.c = 3; //o.d = 4; //编译错误 } }
演示同包的
package ooday04_vis; import ooday04.Coo; public class Eoo { void show(){ Coo o = new Coo(); o.a = 1; //o.b = 2; //编译错误 //o.c = 3; //编译错误 //o.d = 4; //编译错误 } }
演示
protected
-----跨包继承class Foo extends Coo{ void show(){ a = 1; b = 2; //c = 3; //编译错误 //d = 4; //编译错误 } }
14.static
英文 | 翻译 |
---|---|
static | 静态的 |
静态变量
-
由
static
修饰 -
属于类,存储在方法区中,只有一份
-
常常通过类名点来访问
-
何时用:所有对象所共享的数据(图片、音频、视频等)
-
演示静态变量
class Goo{ int a; static int b; Goo(){ a++; b++; } void show(){ System.out.println("a="+a+",b="+b); } }
public class StaticDemo { public static void main(String[] args) { Goo o1 = new Goo(); o1.show(); Goo o2 = new Goo(); o2.show(); Goo o3 = new Goo(); o3.show(); System.out.println(Goo.b); //常常通过类名点来访问 } }
输出结果
a=1,b=1 a=1,b=2 a=1,b=3 3
-
静态方法
-
由
static
修饰 -
属于类,存储在方法区中,只有一份
-
常常通过类名点来访问
-
静态方法中没有隐式
this
传递,不能直接访问实例成员 -
何时用:方法的操作与对象无关(不需要访问对象的属性)
-
演示静态方法
class Hoo{ int a; //实例变量(由对象来访问) static int b; //静态变量(由类名来访问) void show(){ //有this System.out.println(this.a); System.out.println(Hoo.b); }
静态方法中没有隐式
this
传递,没有this
就意味着没有对象,而实例变量a是必须由对象来访问的,所以如下代码发生编译错误static void test(){ //没有this //System.out.println(a); //编译错误 System.out.println(Hoo.b); } }
-
演示何时设计为静态方法
class Ioo{ int a; //对象的属性
在show()中访问了对象的属性,意味着show()方法与对象有关,不能设计为静态方法
void show(){ System.out.println(a); }
在plus()中没有访问对象的属性,意味着plus()方法与对象无关,可以设计为静态方法
static int plus(int num1,int num2){ int num = num1+num2; return num; } }
测试类—mian方法
public class StaticDemo { public static void main(String[] args) { Hoo.test(); //常常通过类名点来访问 } }
-
静态块
-
由
static
修饰 -
属于类,在类被加载期间自动执行,因一个类只被加载一次,所以静态块只执行一次
-
何时用:加载/初始化静态资源(图片、音频、视频等)
-
一般情况下:在静态块中给静态变量赋初始值,在构造方法中给实例变量赋初始值
-
演示静态块
class Joo{ static{ System.out.println("静态块"); } Joo(){ System.out.println("构造方法"); } }
测试类
public class StaticDemo { public static void main(String[] args) { Joo o4 = new Joo(); Joo o5 = new Joo(); Joo o6 = new Joo(); } }
输出结果
静态块 构造方法 构造方法 构造方法
-
15.final
英文 | 翻译 |
---|---|
final | 最终的 不可改变的 |
-
修饰变量:变量不能被改变
class Aoo{ final int a = 5; void show(){ //a = 55; //编译错误,final的变量不能被改变 } }
-
修饰方法:方法不能被重写
class Boo{ final void show(){ } void test(){ } } class Coo extends Boo{ //void show(){ } //编译错误,final的方法不能被重写 void test(){ } }
-
修饰类:类不能被继承,但是可以继承别的类
final class Doo{} //class Eoo extends Doo{} //编译错误,final的类不能被继承 class Foo{} final class Goo extends Foo{} //正确,不能当老爸,但能当儿子
16.static final
常量
应用率高
-
常量必须声明并同时初始化
-
常常通过类名点来访问,不能被改变
-
建议:常量所有字母都大写,多个单词之间用_分隔
-
编译器在编译时会将常量直接替换为具体的数,效率高
-
何时用:数据永远不变,并且经常使用
-
演示常量
class Ioo{ public static int num = 5; //静态变量 public static final int COUNT = 5; //常量 } class Hoo{ public static final double PI = 3.14159; //public static final int NUM; //编译错误,常量必须声明同时初始化 }
测试
Hoo
类public class StaticFinalDemo { public static void main(String[] args) { System.out.println(Hoo.PI); //常常通过类名点来访问 //Hoo.PI = 3.1415926; //编译错误,常量不能被改变
//1)加载Ioo.class到方法区中 //2)静态变量num一并存储在方法区中 //3)到方法区中获取num的值并输出 System.out.println(Ioo.num);
//编译器在编译时会将常量直接替换为具体的值,效率高 //相当于System.out.println(5); System.out.println(Ioo.COUNT); } }
-
17.抽象方法
英文 | 翻译 |
---|---|
abstract | 抽象的 |
- 由
abstract
修饰,只有方法的定义,没有具体的实现(连方法体{}都没有)
设计规则
-
将派生类所共有的属性和行为,抽到超类中---------------------抽共性
-
若派生类的行为/代码都一样,设计为普通方法
若派生类的行为/代码不一样,设计为抽象方法
-
…下周一讲
18.抽象类
- 由
abstract
修饰 - 包含抽象方法的类必须是抽象类
- 抽象类不能被实例化(
new
对象) - 抽象类是需要被继承的,派生类:
- 重写抽象类中的所有抽象方法-----------变不完整为完整
- 也声明为抽象类------------------------------一般不这么做
- 抽象类的意义:
- 封装共有的属性和行为---------------------代码复用
- 为所有派生类提供统一的类型------------向上造型(代码复用)
- 可以包含抽象方法,为所有派生类提供统一的入口(向上造型后能点出来),同时可以达到强制必须重写的目的
19.抽象方法/抽象类的疑问
抽象方法存在的意义是什么?
- 保证当发生向上造型时,通过超类的引用能点出来那个方法-----------保证能点出方法来
既然抽象方法的意义是保证能点出来,那为什么不设计为普通方法呢?
- 设计为普通方法,意味着派生类可以重写也可以不重写,但设计为抽象方法,则可以强制派生类必须重写-----------强制派生类重写,可以达到统一的目的
20.成员内部类
应用率不高
-
类中套类,外面的称为外部类,里面的称为内部类
-
内部类只服务于外部类,对外不具备可见性
-
内部类对象通常在外部类中创建
-
内部类中可以直接访问外部类的成员(包括私有的),
内部类中有一个隐式的引用指向了创建它的外部类对象--------外部类名.this(
API
时会用) -
何时用:若一个类A只被另一个类B使用,还想访问B中的成员,可以设计为成员内部类,访问更加方便
-
演示成员内部类
class Mama{ //外部类 private String name; void create(){ Baby b = new Baby(); //正确,内部类通常在外部类中创建 } class Baby{ //成员内部类 void showName(){ System.out.println(name); System.out.println(Mama.this.name);//调用外部类的name属性 //System.out.println(this.name); //编译错误,this指代当前Baby对象 } } }
测试类
public class InnerClassDemo { public static void main(String[] args) { Mama m = new Mama(); //Baby b = new Baby(); //编译错误,内部类对外不具备可见性 } }
-
外部类名.this
指代当前对象的外部类对象
21.匿名内部类
应用率高------------------------简化代码的操作
自己理解:
是内部类没有名字,并不是匿名内部类的对象没有名字
外部类里边可以有多个内部类,但是内部类只能有一个对象
-
何时用:若想创建一个类(派生类)的对象,并且对象只被创建一次,可以设计为匿名内部类
-
匿名内部类中不能修改外面局部变量(指的就是外部类中方法中的变量)的值,因为在此处该变量会默认为
final
的-----API时会用-
演示匿名内部类
abstract class Boo{ abstract void show(); } abstract class Aoo{ }
测试类___上边的是抽象类,所以不是实例化,是能被继承,在进行实例化,下边代码就是给抽象类的派生类new对象
正常的new对象是:Aoo o1 = new Aoo();(Aoo是正常类的时候) 创建派生类同时new对象:Aoo o1 = new Aoo(){};(Aoo是抽象类的时候,{}号里边就是派生类的类体,)
public class NonInnerClassDemo { public static void main(String[] args) {
- 创建了
Aoo
的一个派生类,但是没有名字.为该派生类创建了一个对象,名为o1---new Aoo(){}
是在创建Aoo
的派生类的对象,大括号中的为派生类的类体
Aoo o1 = new Aoo(){ };
- 创建了
Aoo
的一个派生类,但是没有名字.为该派生类创建了一个对象,名为o2
,大括号中的为派生类的类体
Aoo o2 = new Aoo(){ };
- 创建了
Boo
的一个派生类,但是没有名字,为该派生类创建了一个对象,名为o3
,大括号中的为派生类的类体
Boo o3 = new Boo(){ void show(){ System.out.println("showshow"); //num = 66; //编译错误,在此处num会默认为final的,所以不能修改 } }; o3.show(); } }
- 创建了
-
22.接口
英文 | 翻译 |
---|---|
interface | 接口 |
-
是一种引用数据类型
-
由
interface
定义 -
只能包含常量和抽象方法(所有数据默认都是常量,所有方法默认都是抽象方法)
注:接口中成员的访问权限默认都是
public
的 -
接口不能被实例化(
new
对象) -
接口是需要被实现/继承的,实现类/派生类:必须重写所有抽象方法,和抽象类一样都是必须重写所有抽象方法
-
一个类可以实现多个接口,用逗号分隔。若又继承又实现时,应先继承后实现
-
接口可以继承接口
-
演示接口的语法
interface Inter{ public static final int NUM = 5; public abstract void show(); int COUNT = 5; //默认public static final void test(); //默认public abstract //int number; //编译错误,常量必须声明同时初始化 //void say(){ } //编译错误,抽象方法不能有方法体 } interface Inter1{ void show(); //默认访问权限为public void test(); } interface Inter2{ void show(); } interface Inter3{ void test(); } interface Inter4{ void show(); }
abstract class Boo{ abstract void say(); }
-
演示接口的实现
重写接口中的抽象方法时,必须是public权限
class Aoo implements Inter1{ public void show(){} public void test(){} }
-
演示接口的多实现,继承类
class Coo extends Boo implements Inter2,Inter3{ public void show(){} public void test(){} void say(){} }
-
演示接口继承接口
interface Inter5 extends Inter4{ void test(); } class Doo implements Inter5{ public void test(){} public void show(){} }
-
测试类
public class InterfaceDemo { public static void main(String[] args) { //Inter5 o1 = new Inter5(); //编译错误,接口不能被实例化 Inter5 o2 = new Doo(); //向上造型 Inter4 o3 = new Doo(); //向上造型 } }
-
23.多态
英文 | 翻译 |
---|---|
instanceof | 运算符 |
多种形态
意义
-
同一个对象被造型为不同的类型时,有不同的功能---------所有对象都是多态的(明天详细讲)
-----对象的多态:我、你、水…
-
同一类型的引用在指向不同的对象时,有不同的实现------所有抽象方法都是多态的
-----行为的多态:
cut()
、move()
、getImage()
、getScore()
…
向上造型/自动类型转换
-
超类型的引用指向派生类的对象
-
能点出来什么,是看引用的类型。
重写方法被调用时,看对象的类型。
-
能造型成为的类型有:超类+所实现的接口
向下转型/强制类型转换
-
成功的条件只有如下两种:
-
引用所指向的对象,就是该类型
-
引用所指向的对象,继承了该类或实现了该接口
-
强转时若不符合如上条件,则发生
ClassCastException
类型转换异常
instanceof
- 建议在强转之前先通过
instanceof
来判断引用指向的对象是否是该类型
何时需要强转:若想访问的变量/方法在超类中没有,则需要强转
-
演示向下转型
interface Inter{ } //大 class Aoo{ } //大 class Boo extends Aoo implements Inter{ } //小 class Coo extends Aoo{ } //小
测试类
public class MultiTypeDemo { public static void main(String[] args) { //成功的条件1:引用所指向的对象,就是该类型 //成功的条件2:引用所指向的对象,继承了该类或实现了该接口 Aoo o = new Boo(); //向上造型 Boo o1 = (Boo)o; //引用o所指向的对象,就是Boo类型 Inter o2 = (Inter)o; //引用o所指向的对象,实现了Inter接口 //Coo o3 = (Coo)o; //运行时发生ClassCastException类型转换异常 if(o instanceof Coo){ //true Coo o4 = (Coo)o; } System.out.println(o instanceof Boo); //true System.out.println(o instanceof Inter); //true System.out.println(o instanceof Coo); //false } }
24.面向对象三大特征
封装
- 类:封装的是对象的属性和行为
- 方法:封装的是具体的业务逻辑功能实现
- 访问控制修饰符:封装的是具体的访问权限
继承
-
作用:代码复用
-
超类/父类:所有派生类所共有的属性和行为
接口:部分派生类所共有的属性和行为
派生类/实现类/子类:派生类所特有的属性和行为
-
单一继承、多接口实现,具有传递性
多态
多种形态
-
对象多态:通过向上造型来体现的
行为多态:通过方法的重写来体现的
-
向上造型、强制类型转换(向下转型)
25.String
字符串类型
-
java.lang.String
类使用final修饰,不能被继承 -
String的底层封装的是一个字符数组
-
String在内存中采用Unicode编码格式{0~65535),每个字符占用2个字节的空间----2个字节最大值就是65535
-
字符串对象一旦创建,对象内容永远无法改变,但字符串引用可以重新赋值(指向新的对象)
String
为不变对象
26.字符串常量池
-
java
对String
字符串有一个优化措施:字符串常量池 -
java
推荐我们使用字面量/直接量(直接"")的方式创建字符串对象,并且会缓存所有以字面量方式来创建的字符串对象到常量池中,当使用相同字面量再创建字符串对象时将会复用常量池中的对象,以减少内存开销。-
演示
public class StringDemo { public static void main(String[] args) {
- 使用字面量(直接"")来创建字符串对象时,
JVM
会检查常量池中是否有该对象:- 若没有,则会创建字符串对象,并将其引用存入到常量池中
- 若有,则直接将常量池中的对象(引用)返回—并不会创建新的字符串对象
String s1 = "123abc"; //常量池还没有,因此创建该字符串对象,并存入常量池中 String s2 = "123abc"; //常量池中已经有了,直接复用对象 String s3 = "123abc"; //常量池中已经有了,直接复用对象
- 引用类型
==
,比较的是地址是否相同
System.out.println(s1==s2); //true System.out.println(s1==s3); //true System.out.println(s2==s3); //true s1 = s1+"!"; //创建新的字符串对象("123abc!")并将地址赋值给s1 System.out.println(s1==s2); //false } }
- 使用字面量(直接"")来创建字符串对象时,
-
面向对象-连接
做功能的套路
重点之重点
- 先写行为/方法:
- 若为某个对象所特有的行为,就将方法设计在特定的类中
- 若为所有对象所共有的行为,就将方法设计在超类中
- 窗口调用:
- 若为定时发生的,则在定时器
run
中调用 - 若为事件触发的,则在侦听器中调用------------------------不要求
- 若为定时发生的,则在定时器
调用方法的规则
-
方法有返回值,则必须接收。方法没有返回值,则直接调用即可
-
同一个类中的方法,可以直接调用
-
不同类中的方法:
- 若为实例方法,则通过引用变量打点调用
- 若为静态方法,则通过类名打点调用
如何调错
要求:一定先把问题方法锁定好
-
快速锁定问题方法:
- 将调用方法的代码都注释起来,一个一个的放开运行,放开哪个方法出错,说明问题就在哪个方法上
-
打桩:
System.out.println(数据)
; ----------------不能着急,得慢慢来,打得多了就有经验了
类间关系
- 类和类-------------------------------继承
- 接口和接口-------------------------继承
- 类和接口----------------------------实现
设计规则(超类,普通,抽象)
-
将所有派生类所共有的属性和行为,抽到超类中-------------抽共性
-
若派生类的行为/代码都一样,设计为普通方法
若派生类的行为/代码都不一样,设计为抽象方法
-
将部分派生类所共有的属性和行为,抽到接口中(接口的好处)
- 接口是对继承的单根性的扩展-------------------------------------实现多继承
- 接口相当于制定了一个标准、规范
- 实现了接口,意味着就能干那个事,不实现接口,就干不了那个事
体会接口的好处
//复用性好、扩展性好、维护性好------------------高质量代码
//被撞的是ObserveSubmarine-----调用ObserveSubmarine的getScore()-----10分
//被撞的是TorpedoSubmarine-----调用TorpedoSubmarine的getScore()-----40分
//被撞的是NuclearSubmarine-----调用NuclearSubmarine的getScore()-----100分
if(s instanceof EnemyScore){ //------适用于所有实现EnemyScore接口的
EnemyScore es = (EnemyScore)s;
score += es.getScore();
}
//被撞的是MineSubmarine--------调用MineSubmarine的getLife()---------1
//被撞的是NuclearSubmarine-----调用NuclearSubmarine的getLife()------3
if(s instanceof EnemyLife){ //-------适用于所有实现EnemyLife接口的
EnemyLife el = (EnemyLife)s;
int num = el.getLife();
ship.addLife(num);
}
//复用性差、扩展性差、维护性差------------------垃圾代码
if(s instanceof ObserveSubmarine){ //---------只能适用于ObserveSubmarine
ObserveSubmarine os = (ObserveSubmarine)s;
score += os.getScore();
}
if(s instanceof TorpedoSubmarine){ //---------只能适用于TorpedoSubmarine
TorpedoSubmarine ts = (TorpedoSubmarine)s;
score += ts.getScore();
}
if(s instanceof MineSubmarine){ //---------只能适用于MineSubmarine
MineSubmarine ms = (MineSubmarine)s;
int num = ms.getLife();
ship.addLife(num);
}
if(s instanceof NuclearSubmarine){ //---------只能适用于NuclearSubmarine
NuclearSubmarine ns = (NuclearSubmarine)s;
score += ns.getScore();
int num = ns.getLife();
ship.addLife(num);
}
什么是面向对象?
- 以对象为核心来思考问题、解决问题的一种设计思想