1. 初认识
1.1 类和对象
在计算机的世界中,我们经常需要去描述现实中存在的物体,例如:动物、手机、水果、汽车……,这些事物往往存在着诸多复杂的功能和属性,这时候,我们就可以利用类和对象的思想在计算机中进一步描述他们
1.1.1 什么是类?什么是对象?
- 现实生活是由很多很多对象组成的,基于对象抽出了类
- 对象:软件中真实存在的单个的个体/东西
类:类型/类别,代表一类个体 - 类是对象的模板/模子,对象是类的具体的实例
- 类中可以包含:
- 对象的属性/特征/数据-----------------------成员变量
- 对象的行为/动作/功能-----------------------方法
- 一个类可以创建多个对象
- 三大特性:封装、继承、多态
1.1.2 如何创建类?如何创建对象?如何访问成员?
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 = 25;
zs.address = "河北廊坊";
//调用方法
zs.study();
zs.sayHi();
Student ls = new Student();
ls.name = "lisi";
ls.age = 24;
ls.address = "黑龙江佳木斯";
ls.study();
ls.sayHi();
//1)创建了一个Student对象
//2)给成员变量赋默认值
Student ww = new Student();
ww.study();
ww.sayHi();
}
}
补充:
- 类:是一种引用数据类型
- 创建对象:
引用
数据类型 引用类型变量 指向 对象
Student zs = new Student(); //创建一个Student型的引用zs,指向了一个学生对象
//zs代表的就是那个学生对象,用zs就是在用那个对象
- 引用类型的默认值为:null
2. 面向对象相关的各类语法
2.1 构造方法
构造方法:------------------------------------复用给成员变量赋初值的代码
- 作用:给成员变量赋初始值
- 语法:与类同名,没有返回值类型(连void都没有)
- 调用:在创建(new)对象时被自动调用
- 若自己不写构造方法,则编译器默认提供一个无参的构造方法,若自己写了构造方法,则不再默认提供
- 构造方法可以重载
package day006;
public class Test2_构造方法 {
String name;//当存在一个含参的构造方法时,无参构造将不再自动生成...
//public Test2_构造方法(){}
//含参构造
public Test2_构造方法(String n){
name=n;
}
void eat(){
System.out.println("Test2_构造方法.eat()");
}
}
class tt{
public static void main(String[] args) {//注释掉无参的构造也可以运行,说明系统会自动提供一个无参的构造方法
Test2_构造方法 t2 = new Test2_构造方法();
t2.eat();
//t是引用变量,引用的是对象的地址值。
//根据地址值找到对象,并获取对象的数据
Test2_构造方法 t = new Test2_构造方法("张三");
System.out.println(t.name);
}
}
2.2 话题:内存管理
内存管理:由JVM来管理的
- 堆:new出来的对象(包括成员变量、数组的元素)
- 栈:局部变量(包括方法的参数)
- 方法区:存储.class字节码文件(包括静态变量、所有方法)
补充:
若变量为基本数据类型,则装的是具体的数
若变量为引用数据类型,则装的是堆中对象的地址
2.2.1 单一对象内存图
过程说明:
Person p = new Person();
Person p = new Person();//短短这行代码发生了很多事情
1.把Person.class文件加载进内存
2.在栈内存中,开辟空间,存放引用变量p
3.在堆内存中,开辟空间,存放Person对象
4.对成员变量进行默认的初始化
5.对成员变量进行显示初始化
6.执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
7.堆内存完成
8.把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值
2.2.2 多对象内存图
- 变量p和变量p1不是一片空间,p1需要开辟新的空间
- Person p1=new Person,这时只要有new,就会新开辟空间在堆内存中存入对象。
package day000000;
public class Test1 {
public static void main(String[] args) {
//p是引用对象,持有了对于Person对象的地址值的引用
//此时的p,含有属性,但都是默认值
Person p = new Person();
//设置属性值
p.name="lisi";
p.age=20;
//创建p2
Person p2=new Person();
p2.name="zhangsan";
p2.age=10;
}
}
class Person{
//属性--成员变量
String name;
int age;
//行为--方法
void eat(){
System.out.println("吃饭饭");
}
void sleep(){
System.out.println("睡觉觉");
}
}
2.2.3 static的内存图
补充:
成员变量分两种:
- 实例变量:没有static修饰,属于对象的,存储在堆中,有几个对象就有几份,通过引用名(对象)打点来访问
- 静态变量:有static修饰,属于类的,存储在方法区中,只有一份,通过类名打点来访问
2.2.4 null的内存图
null:表示空,没有指向任何对象
- 若引用的值为null,则该引用不能再进行任何操作了,若操作则发生NullPointerException空指针异常
//演示
Student s = new Student();
s = null;
s.name = "纳西妲";//运行时错误,发生NullPointerException空指针异常
2.3 三大特性------继承
说明:
- 继承是面向对象最显著的一个特性。
- 继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
- Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类/超类/基类。
- 这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
- 提高复用性:只要继承父类,就能有一样的功能
===============================================
- 作用:代码复用
- 通过
extends
实现继承- 超类/父类:共有的属性和行为 派生类/子类:特有的属性和行为
- 派生类可以访问派生类的+超类的,但超类不能访问派生类的
- 一个超类可以有多个派生类,但一个派生类只能有一个超类------------单一继承
- 继承具有传递性
- 继承要符合is(是)的关系
入门案例:
package day99999;
public class TTT {
public static void main(String[] args) {
Zi zi = new Zi();
zi.speak();
System.out.println(zi.skin);
System.out.println(zi.addr);
}
}
class Fu{
String skin="黄种人";
String addr="大成都";
public void speak(){
System.out.println("Fu...speak()");
}
}
//通过extends和父类发生继承关系
//所有父类的功能,子类都可以继承过来,注意不能是private的
class Zi extends Fu{
//什么都不写,能不能把父亲的内容复制一份出来
}
补充:
继承的好处:
- 封装共有的属性和行为---------------实现代码复用
- 为所有派生类提供统一的类型------向上造型(实现代码复用)
2.3.1 关键字 super
super:指代当前对象的超类对象
super的用法:
super.成员变量名-------------------访问超类的成员变量(一般都省略,了解即可)
super.方法名()-----------------------调用超类的方法(一般重写发生时,调用超类方法使用)
super()---------------------------------调用超类的构造方法
java规定:构造派生类之前必须构造超类
- 在派生类的构造方法中若没有调用超类的构造方法,则默认super()调超类无参构造
- 在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供
注意:super调超类构造方法必须位于派生类构造方法的第一行(保证先父再子)
入门案例:
public class SuperDemo {
public static void main(String[] args) {
Boo o = new Boo();
}
}
//在派生类的构造方法中若自己调用了超类的构造方法,则不再默认提供
class Coo{
Coo(int a){
}
}
class Doo extends Coo{
Doo(){
super(5);
}
/*
//如下一堆为默认的
Doo(){
super();
}
*/
}
class Aoo{
Aoo(){
System.out.println("超类构造方法");
}
}
//在派生类的构造方法中若没有调用超类的构造方法,则默认super()调超类无参构造
class Boo extends Aoo{
Boo(){
super(); //默认的,调用超类的构造方法
System.out.println("派生类构造方法");
}
}
2.4 三大特性------多态
1、 多态的前提是继承
2、 要有方法的重写
3、 父类引用指向子类对象,如:Animal a = new Dog(); -- 小到大,向上转型
4、 多态中,编译看左边,运行看右边
5、 多态的好处:
- 多态可以让我们不用关心某个对象到底具体是什么类型,就可以使用该对象的某些方法
- 提高了程序的可扩展性和可维护性
1)入门案例:
public class UploadDemo {
public static void main(String[] args) {
Eoo o1 = new Eoo();
o1.a = 1;
o1.show();
//o1.b = 2; //编译错误
//o1.test(); //编译错误,超类不能访问派生类的
Foo o2 = new Foo();
o2.b = 1;
o2.test();
o2.a = 2; //正确
o2.show(); //正确,派生类可以访问超类的
Eoo o3 = new Foo(); //向上造型
o3.a = 1;
o3.show();
//o3.b = 2; //编译错误
//o3.test(); //编译错误,能点出来什么,看引用的类型
}
}
class Eoo{
int a;
void show(){
}
}
class Foo extends Eoo{
int b;
void test(){
}
}
2)多态的使用
前提:多态对象把自己看做是父类类型
- 成员变量: 使用的是父类的
- 成员方法: 由于存在重写现象,所以使用的是子类的
- 静态成员: 随着类的加载而加载,谁调用就返回谁的
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("小狗喜欢玩皮球~");
}
}
3)多态为了统一调用标准
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("橙子需要剥皮");
}
}
2.4.1 向上转型和向下转型
在JAVA中,继承是一个重要的特征,通过extends关键字,子类可以复用父类的功能,如果父类不能满足当前子类的需求,则子类可以重写父类中的方法来加以扩展。
那么在这个过程中就存在着多态的应用。存在着两种转型方式,分别是:向上转型和向下转型
。
- 向上转型:可以把不同的子类对象都当作父类来看,进而屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,统一调用标准。
比如:父类Parent,子类Child
父类的引用指向子类对象:Parent p=new Child();
说明:向上转型时,子类对象当成父类对象,只能调用父类的功能,如果子类重写了父类中声明过的方法,方法体执行的就是子类重过后的功能。但是此时对象是把自己看做是父类类型的,所以其他资源使用的还是父类型的。
比如:花木兰替父从军,大家都把花木兰看做她爸,但是实际从军的是花木兰,而且,花木兰只能做她爸能做的事,在军营里是不可以化妆的。- 向下转型(较少):子类的引用的指向子类对象,过程中必须要采取到强制转型。这个是之前向上造型过的子类对象仍然想执行子类的特有功能,所以需要重新恢复成子类对象
Parent p = new Child();//向上转型,此时,p是Parent类型
Child c = (Child)p;//此时,把Parent类型的p转成小类型Child
其实,相当于创建了一个子类对象一样,可以用父类的,也可以用自己的
说明:向下转型时,是为了方便使用子类的特殊方法,也就是说当子类方法做了功能拓展,就可以直接使用子类功能。
比如:花木兰打仗结束,就不需要再看做是她爸了,就可以”对镜贴花黄”了
2.5 三大特性------封装
概述:
封装是指隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式。好处:
1、 提高安全性
2、 提高重用性
2.5.1 关键字private
是一个权限修饰符,用于修饰成员变量和成员函数,被私有化的成员只能在本类中访问。
想要修改只能,对外提供公共的,get和set方法。
一般情况下:数据(成员变量)私有化(private),行为(方法)大部分都公开化(public)
package day006;
public class Student {
//String name;
//把属性隐藏起来
private String name;
//提供公共的访问方法
//设置公共的赋值方法
public void setName(String n){
name=n;
}
//设置公共的取值方法
public String getName(){
return name;
}
int age;
}
class StDemo{
public static void main(String[] args) {
Student s = new Student();
//不能访问私有的
//s.name="zhangsan";
//System.out.println(s.name);s
//利用setXxx()给属性赋值
s.setName("zhangsan");
//利用getXxx()给属性取值
System.out.println(s.getName());
}
}
2.6 面向对象知识补充
2.6.1 package和import
- package:声明包
- 作用:避免类的命名冲突
- 规定:同包中的类不能同名,但不同包中的类可以同名
- 类的全称:包名.类名
- 建议:包名所有字母都小写,并且常常有层次结构
- import:导入类
- 同包中的类可以直接访问,但不同包中的类不能直接访问,若想访问:
- 先import导入类,再访问类-------------建议
- 类的全称--------------------------------------太繁琐,不建议
2.6.2 访问控制修饰符
作用:保护数据的安全,实现封装,隐藏数据,暴露行为
- public:公开的,任何类
- private:私有的,本类
- protected:受保护的,本类、派生类、同包类
- 默认的:什么也不写,本类、同包类--------java不建议默认权限 注意:
- 类的访问权限只能是public或默认的
- 类中成员的访问权限如上4种都可以
- 访问控制修饰符的访问权限由高到低依次为:public>protected>默认的>private
package ooday05;
public class Aoo {
public int a; //任何类
protected int b; //本类、派生类、同包类
int c; //本类、同包类
private int d; //本类
void show(){
a = 1;
b = 2;
c = 3;
d = 4;
}
}
class Boo{ //----------------------演示private
void show(){
Aoo o = new Aoo();
o.a = 1;
o.b = 2;
o.c = 3;
//o.d = 4; //编译错误
}
}
package ooday05_vis;
import ooday05.Aoo;
public class Coo { //----------------------演示同包的
void show(){
Aoo o = new Aoo();
o.a = 1;
//o.b = 2; //编译错误
//o.c = 3; //编译错误
//o.d = 4; //编译错误
}
}
class Doo extends Aoo{ //跨包继承-----------演示protected
void show(){
a = 1;
b = 2;
//c = 3; //编译错误
//d = 4; //编译错误
}
}
2.6.3 final关键字
概念:
1、 是java提供的一个关键字2、 final是最终的意思
3、 final可以修饰类,方法,成员变量
初衷是因为:java出现了继承后,子类可以更改父类的功能,当父类功能不许子类改变时可以利用final关键字修饰父类。
特点:
1、 被final修饰的类,不能被继承2、 被final修饰的方法,不能被重写
3、 被final修饰的变量是个常量,值不能被更改
4、 常量的定义形式: final 数据类型 常量名 = 值
- 修饰变量:变量不能被改变
class Eoo{
final int a = 5;
int b = 6;
void test(){
//a = 55; //编译错误,final的变量不能被改变
b = 66;
}
}
- 修饰方法:方法不能被重写
class Foo{
final void show(){}
void test(){}
}
class Goo extends Foo{
//void show(){} //编译错误,final的方法不能被重写
void test(){}
}
- 修饰类:类不能被继承
final class Hoo{}
//class Ioo extends Hoo{} //编译错误,final的类不能被继承
class Joo{}
final class Koo extends Joo{} //正确,不能当老爸,但能当儿子
2.6.4 static关键字
概念
1、 是java中的一个关键字2、 用于修饰成员(成员变量和成员方法)
特点
1、 可以修饰成员变量,成员方法2、 随着类的加载而加载,优先于对象加载
3、 只加载一次,就会一直存在,不再开辟新空间
4、 全局唯一,全局共享
5、 可以直接被类名调用
6、 静态只能调用静态,非静态可以随意调用
7、 static不能和this或者super共用,因为有static时可能还没有对象
8、属于类,存储在方法区中,只有一份
9、静态方法中没有隐式this传递,所以不能直接访问实例成员
1)入门案例:
package cn.tedu.oop;
/*本类用作静态static的入门案例*/
/*0.被static修饰的资源统称为静态资源
* 静态资源是随着类加载而加载到内存中的,比对象优先进入内存
* 所以静态资源可以不通过对象,直接通过类名调用*/
public class TestStatic1 {
public static void main(String[] args) {
//5.通过类名直接调用静态资源
Fruit.clean();//我们可以通过类名直接调用静态方法,这个IDEA会提示
System.out.println(Fruit.kind);//我们可以通过类名直接调用静态属性,这个IDEA会提示
//4.创建水果类的对象
Fruit f1 = new Fruit();
Fruit f2 = new Fruit();
f1.grow();
f1.clean();//没有提示,需要自己写
System.out.println(f1.weight);
System.out.println(f1.kind);//没有提示,需要自己写
//6.修改普通变量的值
f1.weight = 6.6;
System.out.println(f1.weight);//6.6
System.out.println(f2.weight);//0.0
/*3.静态资源在内存中只有一份,而且会被全局所有对象共享
* 所以:不管我们使用哪种方式修改了静态变量的值,使用任何方式来查看
* 都是静态变量那个刚刚修改了的值*/
//7.修改静态变量的值
Fruit.kind = "苹果";
System.out.println(Fruit.kind);
System.out.println(f1.kind);
System.out.println(f2.kind);
f1.kind = "猕猴桃";
System.out.println(Fruit.kind);
System.out.println(f1.kind);
System.out.println(f2.kind);
f2.kind = "香蕉";
System.out.println(Fruit.kind);
System.out.println(f1.kind);
System.out.println(f2.kind);
}
}
//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("这个果子长的一看就很好吃~");
}
}
2)static静态调用关系:
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();
}
}
2.6.5 代码块
2.6.5.1 构造代码块与局部代码块
形式:
{ 代码… }
- 构造代码块:
位置: 在类的内部,在方法的外部
作用: 用于抽取构造方法中的共性代码
执行时机: 每次调用构造方法前都会调用构造代码块
注意事项: 构造代码块优先于构造方法加载- 局部代码块
位置: 在方法里面的代码块
作用: 通常用于控制变量的作用范围,出了花括号就失效
注意事项: 变量的作用范围越小越好,成员变量会存在线程安全的问题
测试代码块的加载顺序:
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只能在代码块里使用
}
}
2.6.5.2 静态代码块
形式:
static{}
静态资源随着类的加载而加载,并且只被加载一次,一般用于项目的初始化
特点: 被static修饰,位置在类里方法外
class Poo{
static{
System.out.println("静态块");
}
Poo(){
System.out.println("构造方法");
}
}
public class StaticDemo {
public static void main(String[] args) {
Poo p1 = new Poo();
Poo p2 = new Poo();
Poo p3 = new Poo();
}
}
2.6.5.3 三种代码块的比较
静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
构造代码块:在创建对象时会自动调用,每次创建对象都会被调用,提取构造共性
局部代码块:方法里的代码块,限制局部变量的范围
测试:
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("我是一个局部代码块~");
}
}
}
结论:执行顺序:静态代码块 --> 构造代码块 --> 构造方法 --> 局部代码块
2.6.6 常量
static final常量:应用率高
- 必须声明同时初始化
- 通过类名点来访问,并且不能被改变
- 建议:常量所有字母都大写,多个单词用_分隔
- 编译器在编译时会将常量直接替换为具体的数,效率高
- 何时用:程序运行过程中数据永远不变,并且经常使用
public class StaticFinalDemo {
public static void main(String[] args) {
System.out.println(Aoo.PI); //常常通过类名点来访问
//Aoo.PI = 3.1415926; //编译错误,常量不能被改变
//1)加载Boo.class到方法区中
//2)静态变量num一并存储到方法区中
//3)到方法区中获取num的值并输出
System.out.println(Boo.num);
//编译器在编译时会将常量直接替换为具体的数,效率高
//相当于System.out.println(5);
System.out.println(Boo.COUNT);
}
}
class Boo{
public static int num = 5; //静态变量
public static final int COUNT = 50; //常量
}
class Aoo{
public static final double PI = 3.14159;
//public static final int NUM; //编译错误,常量必须声明同时初始化
}
2.6.7 abstract 关键字—面向抽象编程
2.6.7.1 抽象类
- abstract 可以修饰方法或者类
- 被abstarct修饰的类叫做抽象类,被abstract修饰的方法叫做抽象方法
- 抽象类中可以没有抽象方法
- 如果类中有抽象方法,那么该类必须定义为一个抽象类
- 子类继承了抽象类以后,要么还是一个抽象类,要么就把父类的所有抽象方法都重写
- 多用于多态中
- 抽象类不可以被实例化
package day009;
public class Test1_Animal {
public void eat(){
System.out.println("吃饭饭");
}
}
/*
* 每种动物都需要吃,
* 发现了,方法声明都一样,只是方法体不一样
*
class Dog extends Test1_Animal{
public void eat(){
System.out.println("狗吃肉");
}
}
class Cat extends Test1_Animal{
public void eat(){
System.out.println("猫吃鱼");
}
}*/
//上面的eat()声明都一样,就是方法体不一样,那就只抽取方法声明部分。
//The type Animal must be an abstract class to define abstract methods
abstract class Animal extends Object{
//This method requires a body instead of a semicolon
public abstract void eat();
}
//继承抽象类,并实现抽象方法
//The type Dog must implement the inherited abstract method Animal.eat()
abstract class Dog extends Animal{
//可以实现抽象方法,也可以子类再变成一个抽象类
}
class Cat extends Animal{
public void eat() {
System.out.println("猫吃鱼");
}
}
2.6.7.2 抽象方法
- 由abstract修饰
- 只有方法的定义,没有具体的实现(连{}都没有)
class A{
public void eat(){//声明一样,可以提取
syso("eat...B") }
}
class B{
public void eat(){//声明一样,可以提取
syso("eat。。。A") }
}
abstract class C{
public abstract void eat();
}
补充:
- 抽象方法的意义是什么?
- 保证当发生向上造型时,通过超类的引用能点出那个方法来------------保证能点出来
- 既然抽象方法的意义是保证能点出来,那为什么不设计为普通方法呢?
- 设计为普通方法,意味着派生类可以重写也可以不重写,但设计为抽象方法,则可以强制派生类必须重写----------------强制派生类重写,以达到统一的目的
2.6.7.3 拓展
2.6.7.3.1 abstract注意事项
抽象方法要求子类继承后必须重写。
那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中。用是可以用的,只是没有意义了。
1.private:被私有化后,子类无法重写,与abstract相违背。
2.static:静态优先于对象存在,存在加载顺序问题。
3.final:被final修饰后,无法重写,与abstract相违背。
2.6.8 关键字 this
- 指代当前对象,哪个对象调用方法它指的就是哪个对象
- this只能用在方法中,方法中访问成员变量之前默认有个
this.
- this的用法:
- this.成员变量名----------------------访问成员变量
注意:当成员变量与局部变量同名时,若想访问成员变量,则this不能省略- this.方法名()---------------------------调用方法(一般不用)
- this()-------------------------------------调用构造方法(用得很少)
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(this.name+"在学习...");
}
void sayHi(){
System.out.println("大家好,我叫"+this.name+",今年"+this.age+"岁了,家住"+this.address);
}
}
public class ConsDemo {
public static void main(String[] args) {
//Student zs = new Student(); //编译错误,Student类没有无参构造方法
Student zs = new Student("zhangsan",25,"LF");
Student ls = new Student("lisi",26,"JMS");
zs.sayHi();
ls.sayHi();
}
}
补充:
成员变量和局部变量是可以同名的,使用时默认采取的是就近原则,此时若想访问成员变量,则this不能省略。
3. 异常
前言:
异常贯穿于我们编程中的各个角落,是我们学习编程的过程中无法避免的话题,例如我们学习数组时,遇到过数组下标越界的异常。表达式除0时,会出现算术异常等等……接下来我们就来学习这个话题相关的知识
概述:
异常是一些用来封装错误信息的对象
它由异常的类型、提示信息、报错的行号提示
三部分组成
3.1 异常的继承结构
3.2 异常的处理方式
当程序中遇到了异常,通常有两种处理方式:捕获或者向上抛出
当一个方法抛出异常,调用位置可以不做处理继续向上抛出,也可以捕获处理异常
大家可以结合生活中的例子:如果工作中遇到了问题,我们可以选择自己处理(捕获),或者交给上级处理(抛出)
捕获方式:
抛出方式:
对于不想现在处理或者处理不了的异常可以选择向上抛出
方式:在方法上设置异常的抛出管道,即:
在可能会会发生异常的方法上添加代码:
throws 异常类型
例如:void method1 throws Exception1,Exception2,Exception3{ }
TIPS:方法上有默认的异常管道:RuntimeException
3.3 异常测试
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.学会看报错的行号提示,确定自己报错的位置,哪里不对点哪里
* 注意:源码不会错,要看的是自己写的代码*/
}
}
3.4 扩展
3.4.1 catch 和 throws
异常处理只有两种方式: catch 和 throws,所以必须二选一
由于Java语法本身的特点,需要开发者事先考虑异常如何处理,也就是我们常说的:“未雨绸缪”
对于初级开发者来说,我们可能会捕获,但不处理异常
try {
…
} catch(Exception e) {
}
底层异常,应该向前抛到前面处理
经验少时,不知道该在什么位置捕获处理,应该选择 throws
但是大家需要注意,在异常抛出时,有些异常比如运行时异常,可能并不会强制要求抛出此异常,调用时也没有报错显示需要额外处理,这个时候就需要大家平时多积累,掌握良好的编码习惯了,手动添加代码进行预处理,增强程序的健壮性了。
3.4.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.4.3 throws 与 throw的区别
- throws
用在方法声明处,其后跟着的是异常类的名字
表示此方法会抛出异常,需要由本方法的调用者来处理这些异常
但是注意:这只是一种可能性,异常不一定会发生- throw
用在方法的内部,其后跟着的是异常对象的名字
表示此处抛出异常,由方法体内的语句处理
注意:执行throw一定抛出了某种异常
4. 接口 ---- 面向接口开发
4.1 概述
1.说明:
与之前学习过的抽象类一样,接口( Interface )在Java中也是一种抽象类型,接口中的内容是抽象形成的需要实现的功能,接口更像是一种规则和一套标准.
2.接口格式:
3.接口的特点:
- 通过interface关键字来定义接口
- 通过implements让子类来实现接口
- 接口中的方法全部都是抽象方法(JAVA8)
- 可以把接口理解成一个特殊的抽象类(但接口不是类!!!)
- 类描述的是一类事物的属性和方法,接口则是包含实现类要实现的方法
- 接口突破了java单继承的局限性
- 接口和类之间可以多实现,接口与接口之间可以多继承
- 接口是对外暴露的规则,是一套开发规范
- 接口提高了程序的功能拓展,降低了耦合性
4.2 入门案例
练习-1:创建接口
package cn.tedu.inter;
/*本接口用于创建接口测试*/
/*1.我们通过interface关键字来定义接口*/
public interface Inter {
/*2.接口中可以定义普通方法吗?--不可以!*/
//public void eat(){}
/*3.接口中可以定义抽象方法吗?--可以,接口中的方法都是抽象方法!*/
public abstract void eat();
public abstract void play();
}
练习-2:创建接口实现类
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:创建接口测试类
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();
}
}
4.3 接口的使用
4.3.1 练习: 接口之构造方法
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("我是子实现类的无参构造");
}
}
总结:
1.接口里是没有构造方法的
2.如果一个类没有明确指定它的父类,那么它默认继承顶级父类Object,调用的super()是Object的无参构造
3.ctrl+o
可以查看类的继承状态
4.3.2 练习: 接口之成员变量
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
4.3.3 练习: 接口之成员方法
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();
4.4 接口的多继承多实现
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("客官,马上就查询好啦,稍等一丢丢~");
}
}
4.5 总结
类与类的关系
继承关系,只支持单继承
比如,A是子类 B是父类,A具备B所有的功能(除了父类的私有资源和构造方法)
子类如果要修改原有功能,需要重写(方法签名与父类一致 + 权限修饰符>=父类修饰符)类和接口的关系
实现关系.可以单实现,也可以多实现
class A implements B,C{}
其中A是实现类,B和C是接口,A拥有BC接口的所有功能,只是需要进行方法的重写,否则A就是抽象类接口与接口的关系
是继承关系,可以单继承,也可以多继承
interface A extends B,C{}
其中ABC都是接口,A是子接口,具有BC接口的所有功能(抽象方法)class X implements A{}
X实现类需要重写ABC接口的所有方法,否则就是抽象类 class Aextends B implements C,D{}
其中A是实现类,也是B的子类,同时拥有CD接口的所有功能
这时A需要重写CD接口里的所有抽象方法接口与抽象类的区别
- 接口是一种用interface定义的类型
抽象类是一种用class定义的类型- 接口中的方法都是抽象方法,还有默认方法与静态方法
抽象类中的方法不做限制- 接口中的都是静态常量
抽象类中可以写普通的成员变量- 接口没有构造方法,不可实例化
抽象类有构造方法,但是也不可以实例化- 接口是先天设计的结果,抽象是后天重构的结果
- 接口可以多继承
抽象类只能单继承
5. 内部类
5.1 概述
如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。
就是把类定义在类的内部的情况就可以形成内部类的形式。
A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
5.2 特点
- 内部类可以直接访问外部类中的成员,包括私有成员
- 外部类要访问内部类的成员,必须要建立内部类的对象
- 在成员位置的内部类是成员内部类
- 在局部位置的内部类是局部内部类
5.3 入门案例
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();
}
}
}
5.4 成员内部类
5.4.1 被private修饰
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修饰以后,无法被外界直接创建创建对象使用
所以可以创建外部类对象,通过外部类对象间接访问内部类的资源
5.4.2 被static修饰
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()");
}
}
}
总结:
静态资源访问时不需要创建对象,可以通过类名直接访问
访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问
5.5 局部内部类
5.5.1 入门案例
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);
}
}
5.5.2 匿名内部类
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("我们会越来越强的!");
}
}
总结:
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用
6. 拓展
6.1 静态变量和实例变量的区别
- 在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
- 在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
6.2 向下造型的补充
发生向下造型的前提是发生向上转型,否则会发生运行时异常
package cn.tedu;
/**
* @Author DELTA
* @Date 2023-02-13-16:14
* @Description:
*/
public class OopTest {
public static void main(String[] args) {
//1.向下转型测试
/*
运行时异常:ClassCastException:
cn.tedu.Animal cannot be cast to cn.tedu.Dog
类型转换异常
Dog d = (Dog) new Animal();
*/
//2.发生向下转型前要向上造型
Animal a = new Dog();
Dog d = (Dog) a;
System.out.println(d.age);
System.out.println(d.name);
}
}
class Animal{
String name;
}
class Dog extends Animal{
int age;
}