文章目录
前言
本人发布的一系列资料都是学习备份使用,以及方便日后复习,随着技术的不断提升,每个文章都会持续添加内容,或者是扩展文章,本是秉承着分享的心态,互相学习,互相提升,也希望提出更好的意见,以及学习思路,欢迎留言,欢迎评论,谢谢!!
面向对象概念
在学习面向对象之前,我们可以先了解一下面向过程。
面向过程是一种编程思想,它强调的是过程,意思是我们做任何事,都需要亲力亲为。
面向对象也是一种编程思想,相对于面向过程,我们的身份可以由原来问题的执行者变为指挥者,进而把生活中很多复杂的问题变得简单化
面向过程强调的是过程,比如,把大象装冰箱,一共分3步:
第1步:打开冰箱门
第2步:把大象放进去
第3步:关上冰箱门
而面向对象强调的是结果,比如:
谁负责把大象装进去?
有没有发现,如果是面向对象的话,干这件事的主体并不是你自己,你变成了一个指挥者?
再比如,衣服脏了,我们可以让女盆友帮忙洗,不需要关注中间过程是怎么洗的,就可以拿到干净的衣服,当然,这首先需要有一个对象(加油哦)~
还有,当我们想吃一道菜,其实并不需考虑这道菜是怎么做的,怎么送达的,只需点菜即可.传菜和做菜都有具体的对象来完成具体的功能.我们不需要关注实现的过程,只需要关注结果就好。
这就是我们所说的面向对象的编程思想(OOP,Object Oriented Programming)
如果面试官问什么是面向对象?你答万物皆对象!额,不建议这样做哈,因为我们还没到大佬的这个高度,还是最好举例。
就像是你说 : 空即是色 色即是空…信你个鬼鬼
面向对象的三大特征
- 封装: 把相关的数据封装成一个“类”组件
- 继承: 是子类自动共享父类属性和方法,这是类之间的一种关系
- 多态: 增强软件的灵活性和重用性
类和对象
类
- Java语言最基本单位就是类,相当于类型。
- 类是一类事物抽取共同属性与功能形成的。
- 可以理解为模板或者设计图纸。
注意:类在现实世界并不存在,它只是一种对象的数据类型
对象
每个对象具有三个特点:对象的属性,对象的功能和对象的标识。
- 对象的属性用来描述对象的基本特征。
- 对象的功能用来描述对象的可以完成的操作。
- 对象的标识是指每个对象在内存中都有一个唯一的地址值用于与其他对象进行区分,类似于我们的身份证号。
类和对象的关系
- 我们先创建类,再通过类创建出对象
- 我们可以通过一个类创建出多个对象
- 类是抽象的,对象是具体的
练习:类的创建使用
在编写代码之前,我们需要分析下需求:
比如我们要把手机这一类事物抽象成代码:
那么这个事物的类型就是"手机",可以用类来描述.
类中包含的就是手机抽取出来的共性的属性与功能.
手机的属性:颜色,尺寸,品牌,价格
手机的功能功能:打电话,发短信,听音乐
我们通过class关键字创建类,通过new关键字创建对象。
接下来我们开始编码吧!
创建包: cn.tedu.oop
创建类: TestCreateClass.java
/**本类用作面向对象入门案例
* 设计手机这一类事物:
* 分析属性:品牌 价格 尺寸 颜色
* 分析功能:打电话 发短信 看直播*/
/**在一个java文件中可以写多个class`,但是,被public修饰的class只能有一个
* 而且要求这个公共类的名字必须与文件的名字保持一致*/
public class TestCreateClass {
public static void main(String[] args) {
//help-findAction 或者 Alt+7
//4.创建手机类的对象p1
Phone p1 = new Phone();
//5.通过手机类对象调用手机类的功能
p1.call();
p1.message();
p1.video();
//6.通过手机类对象打印查看属性值
System.out.println(p1.color);//null
System.out.println(p1.price);//0.0
System.out.println(p1.size);//0.0
System.out.println(p1.brand);//null
}
}
//1.通过class关键字创建手机类--用来描述手机这一类事物--属性+功能
class Phone{
//2.定义手机类的属性--用成员变量来描述--位置:类里方法外
String brand;//品牌
double price;//价格
double size;//尺寸
String color;//颜色
//3.定义手机类的功能--用方法来描述--格式:修饰符 返回值类型 方法名(参数列表){方法体}
public void call(){
System.out.println("正在打电话~");
}
public void message(){
System.out.println("正在发短信!");
}
public void video(){
System.out.println("正在看直播~");
}
}
对象在内存中的存储
Java把内存分成5大区域,我们重点关注栈和堆。
- 一般来讲局部变量存在栈中,方法执行完毕内存就被释放
- 对象(new出来的东西)存在堆中,对象不再被使用时,内存才会被释放
- 每个堆内存的元素都有地址值
- 对象中的属性都是成员变量,是有默认值的
TIPS: 栈与队列指的是一种数据的结构。
栈:先进后出(FILO – First In Last Out)
队列:先进先出(FIFO – First In First Out)
对象的创建过程分析
Phone p = new Phone(); 这句代码,再内存中会发生什么呢?
- 在栈内存中开辟一块空间,存放引用类型Phone类型的变量p
- 在堆内存中开辟一块空间,存放Phone类型的对象
- 要给这个对象进行初始化,比如:String brand = null;
- 当对象准备好以后,会生成一个唯一的地址值,然后将此地址值交给引用类型的变量p来保存
- 如果以后想要操作此对象的各种资源,可以通过p变量保存的地址值找到该对象,比如:p.call(); p.price = 66.6;
练习2:创建多个对象
package cn.tedu.oop;
/**本类用作面向对象入门案例
* 设计手机这一类事物:
* 分析属性:品牌 价格 尺寸 颜色
* 分析功能:打电话 发短信 看直播*/
/**在一个java文件中可以写多个class`,但是,被public修饰的class只能有一个
* 而且要求这个公共类的名字必须与文件的名字保持一致*/
public class TestCreateClass {
public static void main(String[] args) {
//help-findAction 或者 Alt+7
//4.创建手机类的对象p1
Phone p1 = new Phone();
//5.通过手机类对象调用手机类的功能
p1.call();
p1.message();
p1.video();
//6.通过手机类对象打印查看属性值
System.out.println(p1.color);//null
System.out.println(p1.price);//0.0
System.out.println(p1.size);//0.0
System.out.println(p1.brand);//null
//7.创建手机类的第2个对象p2
Phone p2 = new Phone();
//8.通过p2对象,调用Phone类的3个方法
p2.call();
p2.message();
p2.video();
//9.给p2对象的4个属性赋值
p2.brand = "HUAWEI";
p2.color = "中国红";
p2.price = 6666.66;
p2.size = 5.6;
//10.打印查看p2对象的4个属性值
System.out.println(p2.brand);
System.out.println(p2.price);
System.out.println(p2.color);
System.out.println(p2.size);
//11.查看两个对象的地址值
System.out.println(p1);//cn.tedu.oop.Phone@1b6d3586
System.out.println(p2);//cn.tedu.oop.Phone@4554617c
}
}
//1.通过class关键字创建手机类--用来描述手机这一类事物--属性+功能
class Phone{
//2.定义手机类的属性--用成员变量来描述--位置:类里方法外
String brand;//品牌
double price;//价格
double size;//尺寸
String color;//颜色
//3.定义手机类的功能--用方法来描述--格式:修饰符 返回值类型 方法名(参数列表){方法体}
public void call(){
System.out.println("正在打电话~");
}
public void message(){
System.out.println("正在发短信!");
}
public void video(){
System.out.println("正在看直播~");
}
}
p2.brand = “HUAWEI”;
就是先到栈内存中找到p2中保存的唯一的地址值
然后根据地址值找到对应的Phone对象,并对其对应的属性值进行修改
p2.eat();
也是先到栈内存中找到p2中保存的唯一的地址值
然后根据地址值找到对应Phone对象,执行Phone对象的eat()方法
封装
概述
封装是隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式,比如类和方法
好处:
- 提高安全性
- 提高重用性
private关键字
是一个权限修饰符 ,可以用来修饰成员变量和成员方法.被私有化的成员只能在本类中访问
TIPS:如何封装?封装后的资源如何访问?
我们可以使用private关键字来封装成员变量与方法
如何访问私有资源?
关于成员变量:
setXxx – 对外提供公共的设置值方式
getXxx – 对外提供公共的获取值方式
关于成员方法:
把私有方法放在公共方法里供外界调用即可
访问控制符
用来控制一个类,或者类中的成员的访问范围
TIPS:default是表示不写修饰符,默认,如果写default单词来修饰会报错
拓展 匿名对象
没有名字的对象,是对象的简化表示形式。
使用场景:
当被调用的对象只调用一次时(多次会创建多个对象浪费内存)
Demo d = new Demo();
d.sleep();
d.game();
//这个d就是对象的名字。
也可以写成:
new Demo().show();//创建了一个对象调方法
new Demo().game();//又创建了一个对象调方法
构造方法
概念
构造方法是一种特殊的方法,它是一个与类同名且没有返回值类型的方法
构造方法的主要功能就是完成对象创建或者初始化
当类创建对象(实例化)时,就会自动调用构造方法
构造方法与普通方法一样也可以重载.
形式
与类同名,且没有返回值类型,可以含参也可以不含参
修饰符 方法名 ([参数列表]){注意:方法名与类名一样
代码......
}
构造方法创建对象/构造方法赋值
创建包: cn.tedu.oop
创建类: TestConstructor.java
package cn.tedu.oop;
/*本类用于测试构造方法*/
public class TestConstructor {
public static void main(String[] args) {
//4.创建Perosn类的对象进行测试
/*1.每次new(实例化)对象时,都会自动触发对应类的构造方法*/
/*2.每一个类都会默认存在一个没有参数的构造方法
* 但是,如果你提供了其他的构造函数,默认的无参构造会被覆盖
* 所以,我们要手动的提供无参构造,这样才能不传参数,也能创建对象*/
Person p = new Person();
System.out.println(p.age);
System.out.println(p.name);
System.out.println(p.address);
p.eat();
//6.创建Person类的第二个对象
/*4.每次创建对象时,都会执行构造方法
* 构造方法的作用:用来创建对象的*/
Person p2 = new Person();
//8.触发含参构造
Person p3 = new Person(88);
System.out.println(p3.name);
Person p4 = new Person("张三",18,"柳树下");
System.out.println(p4.name);
System.out.println(p4.age);
System.out.println(p4.address);
}
}
//1.创建Person类,用来描述人这一类事物
class Person{
//2.属性--成员变量
String name;//姓名
int age;//年龄
String address;//住址
//8.创建本类的全参构造
//右键->Generate->Constructor->Shift全选所有属性->OK
public Person(String name, int age, String address) {
this.name = name;//局部变量name的值赋值给this指定的成员变量name
this.age = age;//局部变量age的值赋值给this指定的成员变量age
this.address = address;//局部变量address的值赋值给this指定的成员变量address
System.out.println("我是Person类的全参构造");
}
/*3.构造方法的格式:没有返回值类型并且与类名同名的方法*/
//5.创建本类的无参构造
public Person(){
System.out.println("我是Person类的无参构造~");
}
//7.创建本类的含参构造
public Person(int n){
System.out.println("我是Person类的含参构造"+n);
}
//3.功能--方法
public void eat(){
System.out.println("干饭不积极,思想有问题~");
}
}
关于构造函数怎么记忆:
- 特点:方法名与类名相同,且没有返回值类型
- 执行时机:创建对象时立即执行
- 默认会创建无参构造,但是,如果自定义了含参构造,默认的无参构造会被覆盖,注意要手动添加哦
构造代码块与局部代码块
形式:
{ 代码… }
构造代码块的特点
- 位置: 在类的内部,在方法的外部
- 作用: 用于抽取构造方法中的共性代码
- 执行时机: 每次调用构造方法前都会调用构造代码块
- 注意事项: 构造代码块优先于构造方法加载
局部代码块
- 位置: 在方法里面的代码块
- 作用: 通常用于控制变量的作用范围,出了花括号就失效
- 注意事项: 变量的作用范围越小越好,成员变量会存在线程安全的问题
测试代码块的加载顺序
创建包: cn.tedu.oop
创建类: TestBlock.java
package cn.tedu.oop;
/*本类用于测试代码块
执行顺序:构造代码块->构造方法->普通方法->局部代码块,分析:
1.当创建对象时,会触发构造函数
2.创建对象时,也会触发构造代码块,并且构造代码块优先于构造方法执行
3.我们创建好对象后才能通过对象调用普通方法
4.如果普通方法里有局部代码块,才会触发对应的局部代码块 */
public class TestBlock {
public static void main(String[] args) {
//5.分别触发3个构造函数创建对象
Pig p1 = new Pig();//触发的是无参构造
Pig p2 = new Pig("佩奇");//触发的是含参构造
Pig p3 = new Pig("肉包子",5);//触发的是全参构造
//6.通过创建好的对象进行测试
System.out.println(p1.age);//0,默认值
System.out.println(p2.age);//0,默认值
System.out.println(p3.age);//5,创建对象时赋值的
p1.eat();
p2.eat();
p3.eat();
}
}
//1.创建一个小猪类用来测试
class Pig{
//2.定义属性
String food;//食物
int age;//年龄
//7.创建本类的构造代码块
{
/*构造代码块:{}
* 1.位置:类里方法外
* 2.执行时机:每次创建对象时都会执行构造代码块,并且构造代码块优先于构造方法执行
* 3.作用:用于提取所有构造方法的共性功能*/
System.out.println("我是一个构造代码块");
System.out.println("黑猪肉!");
}
//4.1创建本类的无参构造
public Pig(){
//System.out.println("黑猪肉~");
System.out.println("我是Pig类的无参构造");
}
//4.2创建本类的含参构造
public Pig(String s){
//System.out.println("黑猪肉~");
System.out.println("我是Pig类的含参构造"+s);
}
//4.3创建本类的全参构造
//右键->Generate->Constructor->Shift全选所有属性->ok
public Pig(String food, int age) {
//System.out.println("黑猪肉~");
System.out.println("我是Pig类的全参构造");
this.food = food;
this.age = age;
}
//3.创建普通方法
public void eat(){
System.out.println("小猪爱吃菜叶子");
//8.创建本类的局部代码块
{
/*局部代码块:{}
1.位置:方法里
2.执行时机:调用本局部代码块所处的方法时才会执行
3.作用:用于控制变量的作用范围,变量的作用范围越小越好
* */
System.out.println("我是一个局部代码块");
int i = 100;
System.out.println(i);
}
//System.out.println(i);//局部代码块中的局部变量i只能在代码块里使用
}
}
this
概念
this代表本类对象的一个引用对象
形式
this.name = name;
this练习之变量名相同时使用
使用包: cn.tedu.oop
使用类: TestThis1.java
package cn.tedu.oop;
/*本类用于this测试*/
public class TestThis1 {
public static void main(String[] args) {
//4.创建对象并调用方法
Cat c = new Cat();
c.eat();
}
}
//1.创建小猫类
class Cat{
//5.创建成员变量
int count = 666;
int sum = 100;
//2.创建方法
public void eat(){
//3.创建局部变量
int sum = 10;
System.out.println(sum);//10,使用的是局部变量,就近原则
/*当成员变量与局部变量同名时,可以使用this指定本类的成员变量
* 如果不使用this指定,打印的就是近处的这个局部变量,就近原则*/
System.out.println(this.sum);//100
System.out.println(count);//666
}
}
this练习之构造方法间的调用
创建包: cn.tedu.oop
创建类: TestThis2.java
package cn.tedu.oop;
/*本类用于测试this的用法2*/
public class TestThis2 {
public static void main(String[] args) {
//3.1触发无参构造创建本类对象
Dog d1 = new Dog();
//3.2触发含参构造创建本类对象
//Dog d2 = new Dog("旺财");
}
}
//1.创建小狗类
class Dog{
//2.1创建本类的无参构造
public Dog(){
/*在无参构造中,调用含参构造的功能
* 注意:调用是单向的,不能来回双向调用,否则会死循环*/
this("小旺旺");
System.out.println("无参构造");
}
//2.2创建本类的含参构造String s
public Dog(String s){
/*在含参构造中,调用无参构造的功能
* 规定:this关键字必须在构造函数的第1行*/
//this();
System.out.println("含参构造"+s);
}
}
拓展
创建Teacher类进行OOP综合练习之封装
创建包: cn.tedu.oop
创建类: TestTeacher.java
package cn.tedu.oop;
/**本类用于进行OOP综合练习*/
public class TestTeacher {
public static void main(String[] args) {
Teacher t = new Teacher();//6.调用无参构造创建对象
//7.由于private将Teacher类中的属性都封装了,外界无法直接使用,所以需要使用set()/get()
//t.name;//报错:The field Teacher.name is not visible
System.out.println(t.getName());//如果没有设置值或者是设置没有成功,获取的是默认值null
t.setName("鲁智深");
System.out.println(t.getName());
//8.直接调用全参构造来创建对象并且给对象的属性赋值
Teacher t2 = new Teacher("李逵","壮汉",28,300);
System.out.println(t2.getName()+t2.getAge()+t2.getGender()+t2.getSalary());
}
}
//1.创建Teacher类
class Teacher{/**1.属性 2.get()/set() 3.构造方法*/
//2.1提供属性
//2.2对属性进行封装--通过private关键字进行封装
private String name;
private String gender;
private int age;
private double salary;
/**如果什么构造方法都没有添加,默认会存在无参构造
* 我们创建了丰富的构造方法,是为了给外界提供创建本类对象的多种方式
* 如果自定了含参构造,默认的无参构造会被覆盖,注意手动添加哦
* */
//3.添加无参构造方法
public Teacher() {
System.out.println("我是无参构造");
}
//4.添加全参构造方法
public Teacher(String n,String g,int a,double s) {
//5.在构造方法中为成员变量进行赋值
name = n;//n是局部变量也就是用户调用此构造传入的参数,把参数赋值给成员变量name
gender = g;
age = a;
salary = s;
System.out.println("我是全参构造");
}
//2.3需要给外界提供公共的属性设置与访问方式
//快捷方法:右键空白处-->Source-->Genreate Getters and Setters-->select All-->Generate生成即可
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
创建对象的流程
Person p = new Person();//短短这行代码发生了很多事情
1.把Person.class文件加载进内存
2.在栈内存中,开辟空间,存放引用变量p
3.在堆内存中,开辟空间,存放Person对象
4.对成员变量进行默认的初始化
5.对成员变量进行显示初始化
6.执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
7.堆内存完成
8.把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值
继承
概念
继承是面向对象最显著的一个特征
继承是从已有的类中派生出新的类,新类能吸收已有类的数据属性和行为,并扩展新的能力.
Java继承是会用已存在的类的定义作为基础建立新类的技术
新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性的继承父类(超类/基类)
这种继承使得复用以前的代码非常容易,能够大大的缩短开发的周期,降低开发费用.
特点
- 使用extends关键字来表示继承关系
- 相当于子类把父类的功能复制了一份
- Java只支持单继承
- 继承可以传递(爷爷/儿子/孙子这样的关系)
- 父类的私有成员也会被继承,但由于私有限制访问,所以子类不能使用父类的私有资源
- 继承多用于功能的修改,子类可以在拥有父类功能的同时,进行功能拓展
- 像是is a的关系
继承入门案例
创建包: cn.tedu.oop2
创建类: TestExtends.java
package cn.tedu.oop2;
/*本类用于继承的入门案例*/
public class TestExtends {
public static void main(String[] args) {
//5.分别创建3个类的对象
Animal a = new Animal();
Cat c = new Cat();
MiaoMiao m = new MiaoMiao();
//6.利用对象调用方法进行测试
/*3.继承相当于是子类将父类的功能复制了一份
继承还具有传递性,爷爷的功能会传给爸爸,爸爸的功能会传给孙子*/
a.eat();//爷爷类使用自己的方法
c.eat();//爸爸类可以使用从爷爷类中继承过来的方法
m.eat();//孙子类也可以使用从爷爷类中继承过来的方法
}
}
/*1.我们通过extends关键字建立子类与父类的继承关系,格式:子类 extends 父类*/
/*2.Java只支持单继承,一个子类只能有一个父类,但是一个父类可以有多个子类*/
//1.创建小动物类--爷爷类
class Animal{
//4.添加爷爷类的普通方法
public void eat(){
System.out.println("小动物Animal吃啥都行~");
}
}
//2.创建小猫类--爸爸类
/*6.继承是is a的关系,比如小猫是小动物,MiaoMiao是一只小猫
* 继承要求子类必须是父类的一种下属类型,依赖性非常强,强耦合*/
class Cat extends Animal{
//7.定义爸爸类中的属性
int a = 10;//普通属性
private int b = 100;//私有属性
}
//3.创建MiaoMiao类--孙子类
class MiaoMiao extends Cat{
/*4.子类可以拥有自己独有的方法,实现了功能的拓展,青出于蓝而胜于蓝*/
//8.定义孙子类的方法
public void studyJava(){
System.out.println("正在学Java");
System.out.println(a);
/*5.子类继承了父类以后,可以使用父类的所有非私有资源
* 注意:这个私有资源由于被private修饰,所以没有访问权限*/
//System.out.println(b);//不可以,私有资源被限制访问
}
}
super
我们可以把super看作是父类的对象:Father super = new Father();
1.当父类的成员变量与子类的变量同名时,使用super指定父类的成员变量
2.使用super在子类构造方法的第一行调用父类构造方法的功能
super();–调用的是父类的无参构造
super(参数);–调用的是父类对应参数的构造方法
注意:在构造方法里,出现的调用位置必须是第一行
继承的用法
super之继承中成员变量使用
创建包: cn.tedu.oopextends
创建类: TestExtends1.java
package cn.tedu.oop2;
/*本类用于测试继承中变量的使用*/
public class ExtendsDemo1 {
public static void main(String[] args) {
//7.创建子类的匿名对象,调用study()
new Son().study();
}
}
//1.创建父类
class Father{
//3.创建父类的成员变量
int sum = 1;
int count = 2;
}
//2.创建子类
class Son extends Father{
//4.创建子类的成员变量
int sum = 10;
//5.创建子类的普通方法
public void study(){
System.out.println("goog good study , day day up");
//6.创建子类的局部变量
int sum = 100;
//8.打印子类的局部变量sum
System.out.println(sum);//100
//9.打印子类的成员变量sum
System.out.println(this.sum);//10
//10.打印父类的成员变量sum
/*当父类的成员变量与子类的成员变量同名时,可以使用super指定父类的成员变量
* 我们可以把super看作是父类的对象:Father super = new Father();*/
System.out.println(super.sum);//1
System.out.println(count);
}
}
super之继承中构造方法的使用
创建包: cn.tedu.oop2
创建类: ExtendsDemo2.java
package cn.tedu.oop2;
/*本类用于测试继承中构造方法的使用
* 1.子类在创建对象时,默认会先调用父类的构造方法
* 2.原因是子类构造函数中的第一行默认存在super();--表示调用父类的无参构造
* 3.当父类没有无参构造时,可以通过super(参数)调用父类的其他含参构造
* 子类必须调用一个父类的构造函数,不管是无参还是含参,选一个即可
* 4.构造方法不可以被继承!因为语法的原因:要求构造方法的名字必须是本类类名
* 不能在子类中出现一个父类名字的构造方法*/
public class ExtendsDemo2 {
public static void main(String[] args) {
//6.1通过父类的无参构造创建父类对象
//Father2 f = new Father2();
//6.2通过父类的含参构造创建父类对象
//Father2 f2 = new Father2("哈哈哈");
//7.创建子类对象
Son2 s = new Son2();
}
}
//1.创建父类
class Father2{
//3.创建父类的无参构造
// public Father2(){
// System.out.println("我是父类的无参构造");
// }
//4.创建父类的含参构造
public Father2(String s){
System.out.println("我是父类的含参构造"+s);
}
}
//2.创建子类
class Son2 extends Father2{
//5.创建子类的无参构造
public Son2(){
//super();//调用父类的无参构造
super("你好~");
System.out.println("我是子类的无参构造");
}
}
方法重写Override
- 继承以后,子类就拥有了父类的功能
- 在子类中,可以添加子类特有的功能,也可以修改父类的原有功能
- 子类中方法的签名与父类完全一样时,会发生覆盖/复写的现象
- 注意: 父类的私有方法不能被重写
- 重写的要求:两同两小一大
两同:方法名 参数列表 要完全一致
两小:
子类返回值类型小于等于父类的返回值类型(注意此处说的是继承关系,不是值大小)
子类抛出异常小于等于父类方法抛出异常
一大:子类方法的修饰符权限要大于等于父类被重写方法的修饰符权限
继承中成员方法的使用
创建包: cn.tedu.oop2
创建类: ExtendsDemo3.java
package cn.tedu.oop2;
/*本类用于测试继承中方法的使用*/
public class ExtendsDemo3 {
public static void main(String[] args) {
//4.创建对象进行测试
Father f = new Father();
Son s = new Son();
f.eat();
s.eat();
f.play();
s.play();
}
}
//1.创建父类
class Father{
//3.定义父类中的普通方法
public void eat(){
System.out.println("爸爸爱吃肉");
}
public void play(){
System.out.println("爸爸爱放风筝");
}
}
//2.创建子类
class Son extends Father{
//5.如果子类对父类的方法不满意,可以重写父类的方法
/*重写的原则:两同 两小 一大
* 两同:子类方法的 方法名与参数列表 和父类方法的相同
* 一大:子类方法的 方法修饰符权限 >= 父类方法的
* 两小:子类方法的返回值类型 <= 父类方法的返回值类型
* 注意:这里所说的<=是指子类方法的返回值类型是父类返回值类型的子类
* 或者与父类的返回值类型一致,如果父类方法的返回值类型是void,子类保持一致即可
* */
@Override //注解,用来加在方法上,表示这是一个重写的方法
public void eat(){
System.out.println("儿子爱吃蔬菜");
}
@Override
public void play(){
System.out.println("儿子爱玩游戏");
}
}
拓展
this与super的区别
this代表的是本类对象的引用,我们可以把this看作是Cat this = new Cat();
super代表的是父类对象的引用,我们可以把super看作是Father super = new Father();
当本类的成员变量与局部变量同名时,需要使用this.变量名指定本类的成员变量
当本类的成员变量与父类的成员变量同名时,需要使用super.变量名指定父类的成员变量
this可以实现调用本类构造方法的功能,不能互相调用,需要写在构造方法首行
this();表示调用本类的无参构造 this(参数);表示调用本类的对应参数的构造
super也可以实现调用父类构造方法的功能
super();表示调用父类的无参构造 super(参数);表示调用父类的对应参数的构造
注意:super的使用前提是继承,没有父子类关系,就没有super
注意:this调用构造方法或者super调用构造方法,都必须出现在构造方法的第一行
注意:如果父类没有无参构造,需要手动在子类构造方法的第一行调用其他的含参构造
拓展:如果子类重写了父类的方法以后,可以使用super.方法名(参数列表)来调用
重载Overload 与重写Override的区别
重载:在一个类中的现象:同一个类中,存在方法名相同,参数列表不同的方法
重写:是指建立了继承关系以后,子类对父类的方法不满意,可以重写,遵循两同两小一大原则
重载的意义:是为了外界调用方法时方便,不管传入什么样的参数,都可以匹配到对应的同名方法
重写的意义:在不修改源码的情况下,进行功能的修改与拓展(OCP原则:面向修改关闭,面向拓展开放)
static
概念
是java中的一个关键字
用于修饰成员(成员变量和成员方法)
特点
1.static可以修饰成员变量和方法
2.被static修饰的资源称为静态资源
3.静态资源随着类的加载而加载,最先加载,优先于对象进行加载
4.静态资源可以通过类名直接调用,也被称作类资源
5.静态被全局所有对象共享,值只有一份
6.静态资源只能调用静态资源
7.静态区域内不允许使用this与super关键字
练习:static入门案例
创建包: cn.tedu.oop
创建类: TestStatic1.java
package cn.tedu.oop;
/*本类用于静态static的入门案例*/
public class TestStatic1 {
public static void main(String[] args) {
Fruit.clean();//我们可以通过类名直接调用静态方法,而且这个IDEA会提示
System.out.println(Fruit.kind);//有提示,类名直接调用静态属性
Fruit f = new Fruit();
Fruit f2 = new Fruit();
f.grow();
f.clean();//没有提示需要自己写
System.out.println(f.weight);
System.out.println(f.kind);//没提示
/*3.静态资源在内存中只有一份,而且被全局所有对象所共享
* 所以:当我们通过任意一种方式修改了静态变量的值以后
* 不管用何种方式查看,静态变量的值都是刚刚修改后的值*/
Fruit.kind = "苹果";
System.out.println(Fruit.kind);//苹果
System.out.println(f.kind);//苹果
System.out.println(f2.kind);//苹果
f.kind = "香蕉";
System.out.println(Fruit.kind);//香蕉
System.out.println(f.kind);//香蕉
System.out.println(f2.kind);//香蕉
f2.kind = "猕猴桃";
System.out.println(Fruit.kind);//猕猴桃
System.out.println(f.kind);//猕猴桃
System.out.println(f2.kind);//猕猴桃
}
}
/*0.被static修饰的资源统称为静态资源
* 静态资源是随着类加载而加载到内存中的,比对象优先进入内存
* 所以静态资源可以通过类名直接调用,即使没有创建对象,也可以调用*/
//1.创建水果类
class Fruit{
//2.定义属性
/*1.可以用static修饰成员变量吗?可以*/
static String kind;//品种
double weight;//重量
//3.定义普通方法
/*2.可以用static修饰方法吗?可以*/
public static void clean(){
System.out.println("洗水果呀洗水果~");
}
public void grow(){
System.out.println("这个果子长得一看就很好吃~");
}
}
练习:static静态调用关系
创建包: cn.tedu.oopstatic
创建类: TestStatic2.java
package cn.tedu.oopstatic;
/*本类用于测试静态的调用关系*/
/*总结:
* 1.普通资源既可以调用普通资源,也可以调用静态资源
* 2.静态资源只能调用静态资源*/
public class TestStatic2 {
}
//1.创建老师类
class Teacher{
//2.定义普通属性与方法
String name;
public void teach(){
System.out.println("正在授课中...");
/*1.普通资源能否调用静态资源?--可以!!!*/
System.out.println(age);
ready();
}
//3.定义静态属性与方法
static int age;
public static void ready(){
System.out.println("正在备课中...");
/*2.静态资源能否调用普通资源?--不可以!*/
//System.out.println(name);
//teach();
}
public static void eat(){
System.out.println("正在吃饭中...");
/*3.静态资源能否调用静态资源?--可以!*/
System.out.println(age);
ready();
}
}
静态代码块、构造代码块、局部代码块
静态代码块格式
static { }
静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化
特点: 被static修饰,位置在类里方法外
三种代码块的比较
- 静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
- 构造代码块:在创建对象时会自动调用,每次创建对象都会被调用,提取构造共性
- 局部代码块:方法里的代码块,限制局部变量的范围
练习:几种代码块的关系
创建包: cn.tedu. block
创建类: TestBlock.java
package cn.tedu.oopstatic;
/*本类用于学习静态代码块*/
/*执行顺序:
* 静态代码块->构造代码块->构造方法【对象创建成功】->局部代码块*/
public class TestStaticBlock {
public static void main(String[] args) {
//6.创建对象进行测试
Person p = new Person();
Person p2 = new Person();
//7.触发局部代码块
p.play();
}
}
//1.创建Person类
class Person{
//8.创建静态代码块
/*位置:类里方法外
* 执行时机:静态代码块也属于静态资源,随着类的加载而加载,优先于对象加载
* 并且静态资源只会加载一次
* 作用:用于加载那些需要第一时间就加载,并且只加载一次的资源*/
static{
System.out.println("我是静态代码块");
}
//2.创建构造代码块
/*位置:类里方法外
执行时机:每次创建对象时被触发,并且优先于构造方法执行
作用:用于提取所有构造方法的共性功能*/
{
System.out.println("我是构造代码块");
}
//5.创建构造方法
public Person(){
System.out.println("我是无参构造");
}
//3.创建普通方法
public void play(){
System.out.println("我是一个普通方法");
//4.创建局部代码块
/*位置:方法里
* 执行时机:执行本局部代码块所在的方法时才会执行
* 作用:用于限制变量的作用范围*/
{
System.out.println("我是一个局部代码块~");
}
}
}
结论:执行顺序:静态代码块 --> 构造代码块 --> 构造方法 --> 局部代码块
final
概念
- 是java提供的一个关键字
- final是最终的意思
- final可以修饰类,方法,字段(属性)
初衷:java出现继承后,子类可以更改父类的功能,当父类功能不许子类改变时,可以利用final关键字修饰父类。
特点
- 被final修饰的类,不能被继承
- 被final修饰的方法,不能被重写
- 被final修饰的字段是个常量,值不能被修改
- 常量的定义形式:final 数据类型 常量名 = 值
练习:final入门案例
创建包: cn.tedu.oop
创建类: TestFinal.java
package cn.tedu.oop;
/*本类用于测试final关键字*/
public class TestFinal {
}
//1.定义父类
/*1.final可以用来修饰类,被final修饰的类是最终类,不可以被继承
* 可以把被final修饰的类看成树结构中的叶子节点*/
//3.测试类被final修饰
//final class Father2{
class Father2{
//4.定义父类的普通方法
/*2.final可以用来修饰方法,被final修饰的方法是这个方法的最终实现,不可以被重写*/
//6.测试方法被final修饰
//public final void work(){
public void work(){
System.out.println("在工厂里上班~");
}
}
//2.定义子类
class Son2 extends Father2{
final int C = 66;
//5.重写父类的方法
@Override//这个注解用来标记这是一个重写的方法
public void work(){
/*3.被final修饰的是常量,常量的值不可以被修改
* 注意:不管是成员位置还是局部位置,常量定义的时候必须赋值
* 注意:常量的名称必须是全大写,单词与单词之间使用_分割*/
final int B = 100;
//B = 200;//报错:常量的值不可以被修改
System.out.println("在互联网大厂上班~");
System.out.println(Integer.MAX_VALUE);
}
}
多态
概念
多态是面向对象程序设计(OOP)的一个重要特征,指同一个实体同时具有多种形式,即同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。
可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。
比如,你的女盆友让你买点水果回来,不管买回来的是苹果还是西瓜,只要是水果就行,这个就是生活中多态的体现
再比如,小猫、小狗、小猪我们可以把他们都归纳成小动物,每种小动物都需要吃东西,所以我们可以统一设置他们都必须吃,但是每种小动物的习性不一样,那这个就可以设置成小动物自己特有的功能,多态对象只能调用父类中定义子类中重写的功能,并不能调用子类的特有功能,这样就实现了代码的统一
特点
- 多态的前提1:是继承
- 多态的前提2:要有方法的重写
- 父类引用指向子类对象,如:Animal a = new Cat();
- 多态中,编译看左边,运行看右边
练习:多态入门案例
创建包: cn.tedu.oop
创建类: TestDemo.java
package cn.tedu.oop2;
/*本类用作多态的入门案例*/
public class TestDemo {
public static void main(String[] args) {
//6.创建“纯纯的”对象用于测试
Animal a = new Animal();
Cat c = new Cat();
Dog d = new Dog();
a.eat();//小动物Animal吃啥都行~调用的是父类自己的功能
c.eat();//小猫爱吃小鱼干~调用的是子类重写后的功能
d.eat();//小狗爱吃肉骨头~调用的是子类重写后的功能
/*2.父类对象不可以使用子类的特有功能*/
//a.jump();//报错,Animal类里并没有这个方法
//a.run();//报错,Animal类里并没有这个方法
c.jump();//小猫Cat跳的老高啦~,子类可以调用自己的功能
d.run();//小狗Dog跑的老快啦~,子类可以调用自己的功能
//7.创建多态对象进行测试
/*3.口诀1:父类引用指向子类对象
* 解释:创建出来的子类对象的地址值,交给父类类型的引用类型变量来保存*/
Animal a2 = new Cat();//Cat类对象的地址值交给父类型变量a2来保存
Animal a3 = new Dog();//Dog类对象的地址值交给父类型变量a3来保存
//8.测试多态对象
/*4.口诀2:编译看左边,运行看右边
* 解释:必须要在父类定义这个方法,才能通过编译,把多态对象看作是父类类型
* 必须要在子类重写这个方法,才能满足多态,实际干活的是子类*/
a2.eat();//小猫爱吃小鱼干~,多态对象使用的是父类的定义,子类的方法体
}
}
/*1.多态的前提:继承+重写*/
//1.创建父类
class Animal{
//3.创建父类的普通方法
public void eat(){
System.out.println("小动物Animal吃啥都行~");
}
}
//2.1创建子类1
class Cat extends Animal{
//4.1添加重写的方法
public void eat(){
System.out.println("小猫爱吃小鱼干~");
}
//5.1添加子类的特有功能
public void jump(){
System.out.println("小猫Cat跳的老高啦~");
}
}
//2.2创建子类2
class Dog extends Animal{
//4.2添加重写的方法
@Override
public void eat(){
System.out.println("小狗爱吃肉骨头~");
}
//5.2添加子类的特有功能
public void run(){
System.out.println("小狗Dog跑的老快啦~");
}
}
多态的好处
- 多态可以让我们不用关心某个对象到底具体是什么类型,就可以使用该对象的某些方法
- 提高了程序的可扩展性和可维护性
多态的使用
前提:多态对象把自己看做是父类类型
- 成员变量: 使用的是父类的
- 成员方法: 由于存在重写现象,所以使用的是子类的(使用的是父类的方法定义,子类的方法体)
- 如果多态对象调用的是子类没有重写过的方法,方法定义与方法体使用的都是父类的,所以这个不符合多态的前提,直接使用父类对象调用即可
- 静态成员: 随着类的加载而加载,谁调用就返回谁的
练习:多态成员使用测试
创建包: cn.tedu.oop
创建类: TestDemo2.java
package cn.tedu.oop2;
/*本类用于测试多态成员的使用情况*/
public class TestDemo2 {
public static void main(String[] args) {
//7.创建纯纯的子类对象
Dog2 d = new Dog2();
System.out.println(d.sum);//20,子类自己的属性
d.eat();//小狗爱吃肉包子,子类自己的方法
//8.创建多态对象
/*口诀1:父类引用指向子类对象*/
/*口诀2:编译(保存)看左边,运行(效果)看右边*/
Animal2 a = new Dog2();
/*多态中,成员变量使用的是父类的*/
System.out.println(a.sum);//10
/*多态中,方法的声明使用的是父类的,方法体使用的是子类的*/
a.eat();//小狗爱吃肉包子
/*多态中,调用的静态方法是父类的,因为多态对象把自己看作是父类类型
* 直接使用父类中的静态资源*/
a.play();//没有提示,玩啥都行~
Animal2.play();
}
}
//1.创建父类
class Animal2{
//3.创建父类的成员变量
int sum = 10;
//4.创建父类的普通方法
public void eat(){
System.out.println("吃啥都行~");
}
//9.1定义父类的静态方法play
public static void play(){
System.out.println("玩啥都行~");
}
}
//2.创建子类
class Dog2 extends Animal2{
//5.定义子类的成员变量
int sum = 20;
//6.重写父类的方法
@Override
public void eat(){
System.out.println("小狗爱吃肉包子");
}
//9.2创建子类的静态方法play
//@Override
/*这不是一个重写的方法,只是恰巧在两个类中出现了一模一样的两个静态方法
* 静态方法属于类资源,只有一份,不存在重写的现象
* 在哪个类里定义,就作为哪个类的资源使用*/
public static void play(){
System.out.println("小狗喜欢玩皮球~");
}
}
拓展
设计汽车综合案例
创建包: cn.tedu.oopexec
创建类: DesignCar.java
package cn.tedu.oop2;
/*本类用于完成汽车设计案例*/
public class DesignCar {
public static void main(String[] args) {
//9.创建一个纯纯的父类对象进行测试
Car c = new Car();
System.out.println(c.getColor());//null
c.start();
c.stop();
//c.swim();//报错,父类对象不可以调用子类的特有功能
//10.创建纯纯的子类对象做测试
BMW b = new BMW();
System.out.println(b.color);//五彩斑斓的黑
System.out.println(b.getColor());//null
b.start();//都让开,我的车要起飞啦~
b.stop();//唉呀妈呀熄火了~
//11.创建多态对象进行测试
Car c2 = new TSL();
//System.out.println(c2.color);
System.out.println(c2.getColor());
c2.stop();
c2.start();
//c2.swim();
}
}
//1.通过分析,抽象形成一个汽车类
class Car{
//2.定义并封装汽车类的属性--成员变量
private String brand;//品牌
private String color;//颜色
private int id;//编号
private double price;//价格
//3.定义功能
public void start(){
System.out.println("我的小车车启动啦~");
}
public void stop(){
System.out.println("唉呀妈呀熄火了~");
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
//4.创建子类
class BMW extends Car{
String color = "五彩斑斓的黑";
//5.重写父类的方法
@Override
public void start(){
System.out.println("都让开,我的车要起飞啦~");
}
}
//6.创建子类2
class TSL extends Car{
//7.重写父类的方法
@Override
public void stop(){
System.out.println("唉呀妈,怎么停不下来呢");
}
//8.添加子类的特有功能
public void swim(){
System.out.println("没想到吧,我还是个潜水艇");
}
}
多态为了统一调用标准
package cn.tedu.oop2;
public class TestFruit {
public static void main(String[] args) {
Fruit f = new Fruit();
Apple a = new Apple();
Orange o = new Orange();
get(f);
get(a);
get(o);
}
//只需要创建一个方法,就可以执行截然不同的效果
//忽略子类对象的差异统一看作父类类型
public static void get(Fruit f){
f.clean();
}
}
class Fruit{
public void clean(){
System.out.println("水果要洗洗再吃");
}
}
class Apple extends Fruit{
@Override
public void clean(){
System.out.println("苹果需要削皮");
}
}
class Orange extends Fruit{
@Override
public void clean(){
System.out.println("橙子需要剥皮");
}
}
静态变量和实例变量的区别
在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
向上转型和向下转型
在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。
那么在这个过程中就存在着多态的应用。存在着两种转型方式,分别是:向上转型和向下转型。
向上转型:可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。
比如:父类Parent,子类Child
父类的引用指向子类对象:Parent p=new Child();
说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类中声明过的方法,方法体执行的就是子类重过后的功能。但是此时对象是把自己看做是父类类型的,所以其他资源使用的还是父类型的。
比如:花木兰替父从军,大家都把花木兰看做她爸,但是实际从军的是花木兰,而且,花木兰只能做她爸能做的事,在军营里是不可以化妆的。
向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。这个是之前向上造型过的子类对象仍然想执行子类的特有功能,所以需要重新恢复成子类对象
Parent p = new Child();//向上转型,此时,p是Parent类型
Child c = (Child)p;//此时,把Parent类型的p转成小类型Child
其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的
说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。
比如:花木兰打仗结束,就不需要再看做是她爸了,就可以”对镜贴花黄”了
异常
概述
异常是一些用来封装错误信息的对象
它由异常的类型、提示信息、报错的行号提示三部分组成
异常的继承结构
异常的处理方式
当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出
当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常
大家可以结合生活中的例子:如果工作中遇到了问题,我们可以选择自己处理(捕获),或者交给上级处理(抛出)
捕获方式:
抛出方式:
对于不想现在处理或者处理不了的异常可以选择向上抛出
方式:在方法上设置异常的抛出管道,即:
在可能会会发生异常的方法上添加代码:
throws 异常类型
例如:void method1 throws Exception1,Exception2,Exception3{ }
TIPS:方法上有默认的异常管道:RuntimeException
练习:异常测试
创建包: cn.tedu.oop
创建类: ExceptionDemo.java
package cn.tedu.oop;
import java.util.InputMismatchException;
import java.util.Scanner;
/*本类用于异常的入门案例*/
public class ExceptionDemo {
//public static void main(String[] args) throws Exception {//问题实际未处理,还报错
public static void main(String[] args) {
//method1();//调用暴露异常的方法
//method2();//调用解决异常的方法--异常解决方案1--捕获处理--自己解决
/*main()不直接调用会抛出异常的method3()
* 而是调用f(),f()解决了method3()可能会抛出的异常*/
f();
//method3();//调用解决异常的方法--异常解决方案2--向上抛出--交给调用者来解决
}
//相当于在main()调用method3()之前解决了method3()可能会抛出的异常
private static void f() {
try {
method3();
}catch (Exception e){
System.out.println("您输入的数据不对~请重新输入!");
}
}
/*如果一个方法抛出了异常,那么谁来调用这个方法,谁就需要处理这个异常
* 这里的处理也有两种方案:捕获解决 或者 继续向上抛出
* 但注意:我们一般会在main()调用之前将异常解决掉
* 而不是将问题抛给main(),因为没人解决了,该报错还报错*/
/*异常抛出的格式:在方法的小括号与大括号之间,写:throws 异常类型
* 如果有多个异常,使用逗号分隔即可*/
//0.定义一个解决异常的方法-方案2
//private static void method3() throws ArithmeticException,InputMismatchException{
private static void method3() throws Exception{
//1.复写一下刚刚的代码
System.out.println("请您输入要计算的第一个整数:");
int a = new Scanner(System.in).nextInt();
System.out.println("请您输入要计算的第二个整数:");
int b = new Scanner(System.in).nextInt();
System.out.println(a/b);
}
/*异常捕获处理的格式:
* try{
* 可能会抛出异常的代码
* }catch(异常的类型 异常的名字){
* 万一捕获到了异常,进行处理的解决方案
* }
* try-catch结构可以嵌套,如果有多种异常类型需要特殊处理的话
* */
//0.定义一个解决异常的方法-方案1
private static void method2() {
//1.按照捕获处理的格式完成结构
try{
//2.复写一下刚刚的代码
System.out.println("请您输入要计算的第一个整数:");
int a = new Scanner(System.in).nextInt();
System.out.println("请您输入要计算的第二个整数:");
int b = new Scanner(System.in).nextInt();
System.out.println(a/b);
}catch(ArithmeticException e){//异常类型 异常名
System.out.println("除数不能为0!");
}catch (InputMismatchException e){
System.out.println("请输入规定的整数类型!");
/*使用多态的思想,不论是什么子异常,统一看作父类型Exception
* 做出更加通用的解决方案,甚至可以只写这一个,上面2个不写了*/
}catch (Exception e){
System.out.println("您输入的数据不对~请重新输入!");
}
}
//0.定义一个用来暴露异常的方法
private static void method1() {
//1.提示并接收用户输入的两个整数
System.out.println("请您输入要计算的第一个整数:");
int a = new Scanner(System.in).nextInt();
System.out.println("请您输入要计算的第二个整数:");
int b = new Scanner(System.in).nextInt();
//2.输出两个数除法的结果
//输入11和0,报错:ArithmeticException--算术异常,除数不能为0,数学规定
//输入11和3.4,报错:InputMismatchException--输入不匹配异常
System.out.println(a/b);
/*1.不要害怕BUG,真正的勇士敢于直面自己写的BUG*/
/*2.学会看报错的信息提示,确定自己错误的方法*/
/*3.学会看报错的行号提示,确定自己报错的位置,哪里不对点哪里
* 注意:源码不会错,要看的是自己写的代码*/
}
}
拓展1 catch 和 throws
异常处理只有两种方式: catch 和 throws,所以必须二选一
由于Java语法本身的特点,需要开发者事先考虑异常如何处理,也就是我们常说的:“未雨绸缪”
对于初级开发者来说,我们可能会捕获,但不处理异常
try {
…
} catch(Exception e) {
}
底层异常,应该向前抛到前面处理
经验少时,不知道该在什么位置捕获处理,应该选择 throws
但是大家需要注意,在异常抛出时,有些异常比如运行时异常,可能并不会强制要求抛出此异常,调用时也没有报错显示需要额外处理,这个时候就需要大家平时多积累,掌握良好的编码习惯了,手动添加代码进行预处理,增强程序的健壮性了。
拓展2
程序错误分为三种:
编译错误(checked异常);
运行时错误(unchecked异常);
逻辑错误;
- 编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
- 运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。
- 逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。
其实我们还可以手动针对逻辑错误执行异常的抛出动作,大家可以理解成方法return,只不过此处我们返回的是异常,格式:
if(逻辑错误有异常){
AException e = new AException(“提示消息”);
throw e;
}
package cn.tedu;
import java.util.Scanner;
public class TestThrow {
public static void main(String[] args) {
method4();
}
public static void method4(){
//1.复写刚刚可能会发生异常的代码
System.out.println("请输入您要计算的第一个数据:");
int a = new Scanner(System.in).nextInt();
System.out.println("请输入您要计算的第二个数据:");
int b = new Scanner(System.in).nextInt();
try{
double result = divide(a,b);
System.out.println(result);
//System.out.println(a/b);
}catch (ArithmeticException e){
System.out.println("不能除0是我们的错,请鞭笞我们吧!");
}
}
private static double divide(int a,int b) {
if(b == 0){
ArithmeticException e = new ArithmeticException("/ by zero");
throw e;//类似于return e;
}
return a/b;
}
}
拓展3
throws 与 throw 的区别
throws
用在方法声明处,其后跟着的是异常类的名字
表示此方法会抛出异常,需要由本方法的调用者来处理这些异常
但是注意:这只是一种可能性,异常不一定会发生
throw
用在方法的内部,其后跟着的是异常对象的名字
表示此处抛出异常,由方法体内的语句处理
注意:执行throw一定抛出了某种异常
抽象类
概念
Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法.
Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类
- 如果一个类含有抽象方法,那么它一定是抽象类
- 抽象类中的方法实现交给子类来完成
抽象方法的格式
特点
- abstract 可以修饰方法或者类
- 被abstarct修饰的类叫做抽象类,被abstract修饰的方法叫做抽象方法
- 抽象类中可以没有抽象方法
- 如果类中有抽象方法,那么该类必须定义为一个抽象类
- 子类继承了抽象类以后,要么还是一个抽象类,要么就把父类的所有抽象方法都重写
- 多用于多态中
- 抽象类不可以被实例化
练习:抽象类入门案例
创建包: cn.tedu.oop
创建类: AbstractDemo.java
package cn.tedu.oop;
/*本类用作抽象测试的入门案例*/
public class AbstractDemo {
public static void main(String[] args) {
/*4.抽象类不可以实例化!!!-创建对象*/
//5.测试抽象父类是否可以创建对象
//Animal a = new Animal();
//6.创建多态对象进行测试
Animal a = new Pig();
a.eat();//调用抽象父类的普通方法
a.fly();//调用抽象父类的抽象方法
}
}
//1.创建父类Animal
/*2.被abstract修饰的类是抽象类
* 如果一个类中包含了抽象方法,那这个类必须被声明成一个抽象类*/
//4.2添加抽象方法后,本类需要用abstract修饰
abstract class Animal{
//3.创建普通方法
public void eat(){
System.out.println("吃啥都行~");
}
public void play(){
System.out.println("玩啥都行~");
}
//4.1创建抽象方法
/*1.被abstract修饰的方法是抽象方法,抽象方法没有方法体*/
public abstract void fly();
public abstract void fly2();
}
//2.创建子类Pig,并与Animal类建立继承关系
/*3.当一个子类继承了抽象父类以后,有两种解决方案:
* 方案一:变成抽象子类,“躺平,我也不实现,继续抽象”
* 方案二:实现抽象父类中的所有的抽象方法,“父债子偿”*/
//4.3子类需要处理,继续抽象/实现父类所有抽象方法
//abstract class Pig extends Animal{--方案一
class Pig extends Animal{//方案二
@Override//注解,标识这是一个重写的方法
public void fly() {
System.out.println("我爸的债我终于还清了,我家的猪终于飞起来了~");
}
@Override
public void fly2() {
System.out.println("抽象父类中的所有抽象方法都需要被实现");
}
}
练习:抽象类构造函数测试
抽象类中的构造函数通常在子类对象实例化时使用
创建包: cn.tedu.oop
创建类: AbstractDemo2.java
package cn.tedu.oop;
/*本类用作抽象类构造函数测试*/
/*抽象类是否有构造方法?有
* 既然抽象类不能实例化,为什么要有构造方法呢?
* 不是为了自己使用,而是为了子类创建对象时使用super(); */
public class AbstractDemo2 {
public static void main(String[] args) {
//4.测试抽象类是否可以创建对象?不可以!!!
//Animal2 a = new Animal2();
//5.创建子类对象进行测试
Pig2 p = new Pig2();
}
}
//1.创建抽象父类Animal2
abstract class Animal2{
//3.创建构造方法
public Animal2(){
System.out.println("我是Animal2的构造方法~");
}
}
//2.创建子类Pig2
class Pig2 extends Animal2{
//6.创建子类的无参构造
public Pig2(){
super();//表示调用父类的无参构造
System.out.println("我是Pig2的构造方法~");
}
}
练习:抽象类成员测试
创建包: cn.tedu.oop
创建类: AbstractDemo3.java
package cn.tedu.oop;
/*本类用作抽象类中的成员测试*/
public class AbstractDemo3 {
}
//1.创建抽象父类Fruit
abstract class Fruit{
/*1.抽象类中可以定义成员变量吗?--可以!!!*/
//3.1定义抽象父类中的成员变量
int sum = 100;
/*2.抽象类中可以定义成员常量吗?--可以!!!*/
//3.2定义抽象父类中的成员常量
final String name = "XIAOHUANGREN";
/*3.抽象类中可以定义普通方法吗?--可以!!!
* 抽象类中可以都是普通方法吗?--也可以!!!*/
/*4.如果一类中都是普通方法,那它为啥还要被修饰成抽象类呢?
* 因为抽象类不可以被实例化,所以如果不想让外界创建本类的对象
* 就可以把普通类声明成抽象类*/
//4.定义抽象父类的普通方法
public void clean(){
System.out.println("水果还是要洗洗再吃哒~");
}
/*5.抽象类中可以定义抽象方法吗?--可以!!!*/
/*6.如果类中添加了抽象方法,那么这个类必须被声明成抽象类*/
//5.定义抽象父类中的抽象方法
public abstract void grow();
public abstract void clean2();
}
//2.创建子类Banana
/*如果一个子类继承了抽象父类,有两种处理方案:
* 方案一:继续抽象,也就是作为抽象子类,无需实现抽象方法-"躺平"
* 方案二:不再抽象,实现继承自父类中的所有未实现的抽象方法-"父债子偿"*/
class Banana extends Fruit{
@Override
public void grow() {
System.out.println("一串香蕉老沉了~");
}
@Override
public void clean2() {
System.out.println("香蕉不用洗,香蕉喜欢被扒皮");
}
}
拓展
总结:抽象类
可以把它理解为:是众多类中抽取出来的共同特性,多数情况是用来代码重构使用
1.被abstract关键字修饰的类是抽象类
2.被abstract关键字修饰的方法被称为抽象方法,抽象方法没有方法体
3.如果一个类中包含了抽象方法,那么这个类必须被声明成一个抽象类
4.当一个子类继承了抽象父类以后,有两种解决方案:
方案1:变成抽象子类,不实现/实现一部分抽象父类中的抽象方法
方案2:变成普通子类,实现抽象父类中的所有抽象方法,“父债子偿”
5.抽象类不可以实例化?
6.抽象类有构造函数的,但是不是为了自己使用,为了子类super()调用
7.抽象类可以定义成员变量/成员常量
8.抽象类中可以定义全普通方法/全抽象方法/半普通方法半抽象方法
9.如果一个类不想被外界实例化,可以把这个类声明成抽象类
总结:abstract注意事项
抽象方法要求子类继承后必须重写。
那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。
1.private:被私有化后,子类无法重写,与abstract相违背。
2.static:静态优先于对象存在,存在加载顺序问题。
3.final:被final修饰后,无法重写,与abstract相违背。
接口
接口的概念
与之前学习过的抽象类一样,接口( Interface )在Java中也是一种抽象类型,接口中的内容是抽象形成的需要实现的功能,接口更像是一种规则和一套标准.
接口格式
接口的特点
- 通过interface关键字来定义接口
- 通过implements让子类来实现接口
- 接口中的方法全部都是抽象方法(JAVA8)
- 可以把接口理解成一个特殊的抽象类(但接口不是类!!!)
- 类描述的是一类事物的属性和方法,接口则是包含实现类要实现的方法
- 接口突破了java单继承的局限性
- 接口和类之间可以多实现,接口与接口之间可以多继承
- 接口是对外暴露的规则,是一套开发规范
- 接口提高了程序的功能拓展,降低了耦合性
练习
1:创建接口
创建包: cn.tedu.inter
创建接口:Inter.java
package cn.tedu.inter;
/*本接口用于创建接口测试*/
/*1.我们通过interface关键字来定义接口*/
public interface Inter {
/*2.接口中可以定义普通方法吗?--不可以!*/
//public void eat(){}
/*3.接口中可以定义抽象方法吗?--可以,接口中的方法都是抽象方法!*/
public abstract void eat();
public abstract void play();
}
2:创建接口实现类
创建包: cn.tedu.inter
创建接口实现类:InterImpl.java
package cn.tedu.inter;
/*本类作为Inter接口的实现类*/
/*1.实现类如果想要实现接口定义的功能,需要与接口建立实现关系
* 通过关键字implements来建立实现类 实现 接口的关系*/
/*2.1 方案一:如果实现类与接口建立实现关系以后
可以选择不实现接口中的抽象方法,把自己变成一个抽象类*/
//abstract public class InterImpl implements Inter{//方案一
/*2.2方法二:如果实现类与接口建立实现关系以后
* 还可以选择实现接口中的所有抽象方法,把自己变成一个普通子类*/
public class InterImpl implements Inter{
@Override
public void eat() {
System.out.println("吃火锅");
}
@Override
public void play() {
System.out.println("玩代码");
}
}
3:创建接口测试类
创建包: cn.tedu.inter
创建接口测试类:InterTests.java
package cn.tedu.inter;
/*本类用于运行测试接口实现类*/
public class InterTests {
public static void main(String[] args) {
/*接口可以实例化吗?--不可以!!!*/
//Inter i = new Inter();
//创建多态对象进行测试--不常用
Inter i = new InterImpl();
i.eat();
i.play();
//创建纯纯的接口实现类对象进行测试--推荐使用
InterImpl i2 = new InterImpl();
i2.eat();
i2.play();
}
}
接口的用法
练习: 接口之构造方法
创建包: cn.tedu.inter2
创建类:TestUserInter.java
package cn.tedu.inter2;
/**本类用于进一步测试接口的使用*/
public class TestUserInter {
//5.创建入口函数main()
public static void main(String[] args) {
/**查看类的继承结构:Ctrl+O*/
Inter2 i = new Inter2Impl();
}
}
//1.创建接口
interface UserInter{
//2.测试接口中是否包含构造方法
//public UserInter(){}
/*1.接口里没有构造方法*/
}
//3.创建接口的实现类
class UserInterImpl implements UserInter{
//4.创建实现类的构造方法
public UserInterImpl(){
/*2.如果一个类没有明确指定它的父类,那么它默认继承顶级父类Object*/
super();/*3.此处调用的父类的无参构造是Object的无参构造*/
System.out.println("我是子实现类的无参构造");
}
}
总结:接口里是没有构造方法的
如果一个类没有明确指定它的父类,那么它默认继承顶级父类Object,调用的super()是Object的无参构造
练习: 接口之成员变量
package cn.tedu.inter2;
/*本类用于进一步测试接口的使用*/
public class TestUserInter {
public static void main(String[] args) {
//6.测试接口中的静态常量
System.out.println(UserInter.age);//静态,因为可以被接口名直接调用
//UserInter.age = 37;//final,因为值不可以被修改
}
}
//1.创建接口
interface UserInter{
//5.测试接口中是否可以定义成员变量
/*4.接口中的是静态常量,实际上的写法是public static final int age = 20;
* 只不过接口中可以省略不写,会默认拼接,所以写成 int age = 20;也可以*/
public static final int age = 20;
}
//3.创建接口的实现类
class UserInterImpl implements UserInter{
}
总结:接口里没有成员变量,都是常量。所以,你定义一个变量没有写修饰符时,默认会加上:public static final
接口之成员方法
package cn.tedu.inter2;
/*本类用于进一步测试接口的使用*/
public class TestUserInter {
public static void main(String[] args) {
UserInterImpl u = new UserInterImpl();
u.eat();
u.play();
}
}
//1.创建接口
interface UserInter{
//7.测试接口中有抽象方法吗?
/*5.接口中抽象方法的定义可以简写,会自动给方法拼接public abstract*/
public abstract void eat();
void play();
}
//3.创建接口的实现类
class UserInterImpl implements UserInter{
@Override
public void eat() {
System.out.println("实现接口中的抽象方法1");
}
@Override
public void play() {
System.out.println("实现接口中的抽象方法2");
}
}
总结:接口里的方法,默认都是抽象的,方法上会默认拼接public abstract。例如:public abstract void save();
分析示例—面向接口编程
具体事物: 培优班老师 高手班老师
共性: 讲课 备课
创建包: cn.tedu.inter2
创建类:TestTeacherInter.java
package cn.tedu.inter2;
/*本类用于面向接口编程*/
public class TestTeacherInter {
public static void main(String[] args) {
CGBTeacher ct = new CGBTeacher();
ct.ready();
ct.teach();
}
}
//1.创建老师接口--接口是先天设计的结果,最先设计的就是接口,要制定规则
interface Teacher{
//2.定义接口里的方法
void teach();//授课方法
void ready();//备课方法
}
class CGBTeacher implements Teacher{
@Override
public void teach() {
System.out.println("授课电商项目");
}
@Override
public void ready() {
System.out.println("备课电商项目");
}
}
class ACTTeacher implements Teacher{
@Override
public void teach() {
System.out.println("正在授课基础加强+框架加强");
}
@Override
public void ready() {
System.out.println("正在备课基础加强+框架加强");
}
}
abstract class SCDTeacher implements Teacher{
@Override
public void ready() {
System.out.println("正在研发新课程。。。");
}
}
接口的多继承多实现
创建包: cn.tedu.inter2
创建类:TestRelation.java
package cn.tedu.inner2;
import cn.tedu.inter.Inter;
/*本类用于测试接口与类之间的复杂关系*/
public class TestRelation {
public static void main(String[] args) {
//创建对象进行功能测试
Inter3Impl i = new Inter3Impl();
i.save();
i.delete();
i.update();
i.find();
}
}
//1.创建接口1
interface Inter1{
void save();//保存功能
void delete();//删除功能
}
//2.创建接口22
interface Inter22{
void update();//更新功能
void find();//查询功能
}
//3.创建接口1的实现类
class Inter1Impl implements Inter1{
@Override
public void save() { }
@Override
public void delete() { }
}
//4.创建接口3,同时继承两个接口
/*1.接口可以继承接口,并且可以多继承,多个接口之间用逗号隔开*/
interface Inter3 extends Inter1,Inter22{ }
//5.创建接口3的实现类
/*2.接口与实现类是实现的关系,并且可以多实现,多个接口之间用逗号隔开
* 对于Java中的类而言,遵循:单继承 多实现
* 一个类只能有一个父类,但是一个类可以实现多个接口*/
//class Inter3Impl implements Inter3{//写法1
class Inter3Impl implements Inter1,Inter22{//写法2
@Override
public void save() {
System.out.println("稍等...正在努力保存中...");
}
@Override
public void delete() {
System.out.println("删除成功!");
}
@Override
public void update() {
System.out.println("小二正在马不停蹄的更新~");
}
@Override
public void find() {
System.out.println("客官,马上就查询好啦,稍等一丢丢~");
}
}
总结
1. 类与类的关系
继承关系,只支持单继承
比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法)
子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)
2. 类和接口的关系
实现关系.可以单实现,也可以多实现
class A implements B,C{}
其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类
3. 接口与接口的关系
是继承关系,可以单继承,也可以多继承
interface A extends B,C{}
其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法)
class X implements A{}
X实现类需要重写ABC接口的所有方法,否则就是抽象类
class A extends B implements C,D{}
其中A是实现类,也是B的子类,同时拥有CD接口的所有功能
这时A需要重写CD接口里的所有抽象方法
4.接口与抽象类的区别
- 接口是一种用interface定义的类型
抽象类是一种用class定义的类型 - 接口中的方法都是抽象方法,还有默认方法与静态方法
抽象类中的方法不做限制 - 接口中的都是静态常量
抽象类中可以写普通的成员变量 - 接口没有构造方法,不可实例化
抽象类有构造方法,但是也不可以实例化 - 接口是先天设计的结果,抽象是后天重构的结果
- 接口可以多继承
抽象类只能单继承
内部类
内部类概述
如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
特点
- 内部类可以直接访问外部类中的成员,包括私有成员
- 外部类要访问内部类的成员,必须要建立内部类的对象
- 在成员位置的内部类是成员内部类
- 在局部位置的内部类是局部内部类
练习 : 内部类入门案例
创建包: cn.tedu.innerclass
创建类: TestInner1.java
package cn.tedu.innerclass;
/*本类用作测试内部类的入门案例*/
public class TestInner1 {
public static void main(String[] args) {
//3.创建内部类对象,使用内部类的资源
/*外部类名.内部类名 对象名 = 外部类对象.内部类对象*/
Outer.Inner oi = new Outer().new Inner();
oi.delete();
System.out.println(oi.sum);
//4.调用外部类的方法--这样是创建了一个外部类的匿名对象,只使用一次
new Outer().find();
}
}
//1.创建外部类 Outer
class Outer{
//1.1创建外部类的成员变量
String name;
private int age;
//1.2创建外部类的成员方法
public void find(){
System.out.println("Outer...find()");
//6.测试外部类如何使用内部类的资源
//System.out.println(sum);--不能直接使用内部类的属性
//delete();--不能直接调用内部类的方法
/*外部类如果想要使用内部类的资源,必须先创建内部类对象
* 通过内部类对象来调用内部类的资源*/
Inner in = new Inner();
System.out.println(in.sum);
in.delete();
}
//2.创建内部类Inner--类的特殊成员
/*根据内部类位置的不同,分为:成员内部类(类里方法外)、局部内部类(方法里)*/
class Inner{
//2.1定义内部类的成员变量
int sum = 10;
//2.2定义内部类的成员方法
public void delete(){
System.out.println("Inner...delete()");
//5.测试内部类是否可以使用外部类的资源
/*结论:内部类可以直接使用外部类的资源,私有成员也可以!*/
System.out.println(name);
System.out.println(age);
/*注意:此处测试完毕需要注释掉,否则来回调用
* 会抛出异常StackOverFlowException栈溢出异常*/
//find();
}
}
}
成员内部类
练习 : 被private修饰
创建包: cn.tedu.innerclass
创建类: TestInner2.java
package cn.tedu.innerclass;
/**本类用来测试成员内部类被private修饰*/
public class TestInner2 {
public static void main(String[] args) {
/**怎么使用内部类Inner2的资源?*/
//4.创建内部类Inner2对象进行访问
//Outer2.Inner2 oi = new Outer2().new Inner2();
//oi.eat();
/**如果Inner2被private修饰,无法直接创建对象该怎么办?*/
//7.创建外部类对象,间接访问私有内部类资源
new Outer2().getInner2Eat();
}
}
//1.创建外部类Outer2
class Outer2{
//6.提供外部类公共的方法,在方法内部创建Inner2内部类对象,调用内部类方法
public void getInner2Eat() {
Inner2 in = new Inner2();//外部类可以访问内部类的私有成员
in.eat();
}
//2.1创建成员内部类Inner2
/**成员内部类的位置:类里方法外*/
//5.成员内部类,被private修饰私有化,无法被外界访问
private class Inner2{
//3.创建内部类的普通成员方法
public void eat() {
System.out.println("我是Inner2的eat()");
}
}
}
总结:
成员内部类被Private修饰以后,无法被外界直接创建创建对象使用
加粗样式所以可以创建外部类对象,通过外部类对象间接访问内部类的资源
被static修饰
创建包: cn.tedu.innerclass
创建类: TestInner3.java
package cn.tedu.innerclass;
/**本类用来测试成员内部类被static修饰*/
public class TestInner3 {
public static void main(String[] args) {
/**如何访问内部类的show()?*/
//4.创建内部类对象访问show()
//方式一:按照之前的方式,创建内部类对象调用show()
//Outer3.Inner3 oi = new Outer3().new Inner3();
//oi.show();
//方式二:创建匿名内部类对象访问show()
//new Outer3().new Inner3().show();
/**现象:当内部类被static修饰以后,new Outer3()报错*/
//6.用static修饰内部类以后,上面的创建语句报错,注释掉
//通过外部类的类名创建内部类对象
Outer3.Inner3 oi = new Outer3.Inner3();
oi.show();
//7.匿名的内部类对象调用show()
new Outer3.Inner3().show();
//9.访问静态内部类中的静态资源--链式加载
Outer3.Inner3.show2();
}
}
//1.创建外部类Outer3
class Outer3{
//2.创建成员内部类Inner3
//5.内部类被static修饰—并不常用!浪费内存!
static class Inner3{
//3.定义成员内部类中普通的成员方法
public void show() {
System.out.println("我是Inner3类的show()");
}
//8.定义成员内部类的静态成员方法
static public void show2() {
System.out.println("我是Inner3的show2()");
}
}
}
总结:
静态资源访问时不需要创建对象,可以通过类名直接访问
访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问
局部内部类
创建包: cn.tedu.innerclass
创建类: TestInner4.java
package cn.tedu.innerclass;
/**本类用来测试局部内部类*/
public class TestInner4 {
public static void main(String[] args) {
/**如何使用内部类的资源呢?
* 注意:直接调用外部类的show()是无法触发内部类功能的
* 需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
* */
//5.创建外部类对象调用show()
//7.当在外部类show()中创建局部内部类对象并且进行功能调用后,内部类的功能才能被调用
new Outer4().show();
}
}
//1.创建外部类Outer4
class Outer4{
//2.创建外部类的成员方法
public void show() {
//3.创建局部内部类Inner4—不太常用!!!
/**位置:局部内部类的位置在方法里*/
class Inner4{
//4.创建局部内部类的普通属性与方法
String name;
int age;
public void eat() {
System.out.println("我是Inner4的eat()");
}
}
/**如何使用局部内部类的资源?*/
//6.在show()里创建内部类对象
Inner4 in = new Inner4();
in.eat();
System.out.println(in.name);
System.out.println(in.age);
}
}
匿名内部类
创建包: cn.tedu.innerclass
创建类: TestInner5.java
package cn.tedu.innerclass;
/*本类用于测试匿名内部类
* 匿名内部类没有名字,通常与匿名对象结合在一起使用*/
public class TestInner5 {
public static void main(String[] args) {
//传统方式:创建接口的实现类+实现类实现接口中的抽象方法+创建实现类对象+通过对象调用方法
//3.创建接口一对应的匿名对象与匿名内部类,并调用实现了的方法save()
new Inter1(){
@Override
public void save() {
System.out.println("save()...");
}
@Override
public void get() { }
}.save();
//5.创建抽象类对应的匿名对象与匿名内部类
new Inter2(){
@Override
public void drink() {
System.out.println("一人饮酒醉");
}
}.drink();
//7.调用普通类的功能怎么调用?创建匿名对象直接调用
new Inter3().powerUp();
new Inter3().powerUp();//new了2次,所以是两个匿名对象
/*如果想要多次使用实现后的功能,还是要创建普通的对象
* 匿名对象只能使用一次,一次只能调用一个功能
* 匿名内部类其实就充当了实现类的角色,去实现未实现的抽象方法,只是没有名字而已*/
Inter3 in = new Inter3();
in.study();
in.study();
in.study();
in.study();
in.study();
in.study();
}
}
//1.创建接口
interface Inter1{
//2.定义接口中的抽象方法
void save();
void get();
}
//4.创建抽象类
abstract class Inter2{
public void play(){
System.out.println("Inter2...play()");
}
abstract public void drink();
}
//6.创建普通类
class Inter3{
public void study(){
System.out.println("什么都阻挡不了我想学习赚钱的决心");
}
public void powerUp(){
System.out.println("我们会越来越强的!");
}
}
总结:
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用
总结
- 内部类创建对象的格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象 - 根据内部类的位置不同,分为:
成员内部类:(类里方法外)
局部内部类:(方法里) - 内部类可以直接使用外部类的资源,外部类使用内部类资源时,
需要先创建内部类的对象,通过内部类对象来调用 - 成员内部类被private修饰后,需要在外部类创建公共方法,间接访问内部类的资源
- 静态内部类不需要先创建外部类对象,
而是要先通过外部类的类名找到内部类,再创建内部类的对象 - 如果静态内部类有静态资源,可以不创建一个对象,就通过
外部类名.内部类名.静态资源名 的链式加载的方式,使用资源 - 直接创建外部类对象,调用局部内部类所处的方法时,并不会触发内部类的功能
需要在内部类所处的方法中,创建内部类对象,并调用其功能,功能才会被触发 - 匿名内部类没有名字,通常与匿名对象结合在一起使用
- 如果想要多次使用实现后的功能,还是要创建之前的普通对象
匿名对象只能使用一次,一次只能调用一个功能
匿名内部类其实就是充当了实现类的功能,去实现未实现的方法,只是没有名字而已