Java面对对象
Java面向对象
面对对象概述,类与对象,继承,重写与重载,多态,抽象,封装,包,泛型,异常
面对对象概述
什么是面向对象(OOP)
面向对象(Object Oriented)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
面向过程和面向对象
面向过程
**优点:**性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
缺点:不易维护、不易复用、不易扩展
面向对象
**优点:**易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
**缺点:**性能比面向过程差
区别
面向对象是相对于面向过程来讲的,指的是把 相关的数据和方法组织为一个整体 来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。
面向对象核心概念
面向对象的三大特性
封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式
将变化隔离,便于使用,提高复用性和安全性。
继承
对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以使用父类的功能,但不能选择性地继承父类。通过使用继承我们可以非常方便地复用以前的代码。
多态
所谓多态就是指程序中定义的引用变量的指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底就会指向那个类的实例对象,该引用变量发出的方法调用到底是那个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,提高了程序的拓展性。
面向对象三大思想
面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP
OOA:面向对象分析(Object Oriented Analysis)
OOD:面向对象设计(Object Oriented Design)
OOP:面向对象程序(Object Oriented Programming
五大基本原则
单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的。
开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
接口分离原则ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
小结
抽象会使复杂的问题更加简单化。
从以前面向过程的执行者,变成了张张嘴的指挥者。
面向对象更符合人类的思维,面向过程则是机器的思想
类与对象
概述
类 : 类是具有相同属性和服务的一组对象的集合。为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和服务两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,应该有一个类名并包括属性说明和服务说明两个主要部分。
对象:对象是系统中用来描述客观事物的一个实体,是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。从更抽象的角度来说,对象是问题域或实现域中某些事物的一个抽象,它反映该事物在系统中需要保存的信息和发挥的作用;它是一组属性和有权对这些属性进行操作的一组服务的封装体。客观世界是由对象和对象之间的联系组成的。
类与对象的关系
类:是一组相关属性和行为的集合
对象:该类事物的具体体现
类
Java中用class描述事物也是如此
1.成员变量 就是事物的属性
2.成员方法 就是事物的行为
定义类其实就是定义类的成员(成员变量和成员方法)
成员变量和局部变量的区别
成员变量
类中的属性称为成员变量,该变量定义时 格式为:
数据类型 变量名;
注意:
① 在类中定义的成员变量并不需要有初始值
② 对于成员变量来说,其作用域是在整个类中有效
何为局部变量?
- 局部变量就是方法里定义的变量。
- 局部变量仅作用在局部区域中,从定义开始到大括号或者return结束,生命周期短。
- 局部变量存储:基本数据类型变量放在栈中,引用数据类型放在堆中
- 局部变量可以先定义再初始化,也可以定义的同时初始化,局部变量没有默认初始值。
在类中的位置不同
成员变量 类中方法外
局部变量 方法内或者方法声明上
在内存中的位置不同
成员变量 堆内存
局部变量 栈内存
生命周期不同
成员变量 随着对象的存在而存在,随着对象的消失而消失
局部变量 随着方法的调用而存在,随着方法的调用完毕而消失
初始化值不同
成员变量 有默认的初始化值
局部变量 没有默认的初始化值,必须先定义,赋值,才能使用。
类的初始化过程
Student s = new Student();在内存中做了哪些事情?
加载Student.class文件进内存
在栈内存为s开辟空间
在堆内存为学生对象开辟空间
对学生对象的成员变量进行默认初始化
.对学生对象的成员变量进行显示初始化
通过构造方法对学生对象的成员变量赋值
学生对象初始化完毕,把对象地址赋值给s变量
对象的内存图
方法的构造
问题:1.对于创建一个对象格式为:new 类名() => 格式的解释 => 对于类名()方法的调用 => 类名()方法是什么?2.对于当前的类对象赋予其属性值,相对较为麻烦,有没有更好的方式编写对应的代码构造方法:实际上是一个方法,编写构造方法的格式:public 类名(){ => 无参构造}public 类名(数据类型 参数名,...){ => 无参构造}注意:① 构造方法的方法名需要和类名名称一致② 构造方法不需要有返回值,并且连void都不需要编译和反编译:编辑:将.java文件信息编译成进制文件反编译:将进制文件 转换成文本文件 idea提供了反编译的功能,对于.class文件可以直接查看其内容通过idea查看StuConstruct类的编译文件 可以发现有对应的构造方法public StuConstruct() {
匿名对象
匿名对象:就是没有名字的对象。
是对象的一种简化表示形式
匿名对象的两种使用情况
对象调用方法仅仅一次的时候
作为实际参数传递static关键字
可以修饰成员变量和成员方法
static关键字特点
随着类的加载而加载
优先于对象存在
被类的所有对象共享
这也是我们判断是否使用静态关键字的条件
可以通过类名调用
static关键字注意事项
在静态方法中是没有this关键字的
静态方法只能访问静态的成员变量和静态的成员方法
修饰成员属性
static修饰成员属性:① 对象可以调用static修饰的成员变量 并可以对其进行赋值② 通过static修饰的成员变量是所有对象所共有的属性③ 通过static修饰的成员变量是可以通过类名.属性名进行调用 赋值(赋值过后只要不再次赋值,值为最新一次赋的值)④ 通过内存展示static修饰的成员变量
修饰成员方法
成员方法是属于对象的 静态方法 => 属于对象也属于类的
静态方法:① 可以被对象进行调用② 可以直接使用类名进行调用③ 在成员方法中可以直接调用静态的方法④ 在静态方法中不能直接调用成员方法 为什么?当静态方法被调用时,可能是对象调用 也可能是类进行调用,当是类调用时,并没有获取到一个对象,所以不能直接进行使用类进行调用成员方法⑤ 成员方法只能通过对象进行调用而不能通过类名进行调用⑥ 对于静态方法中可以直接使用静态属性⑦ 对于静态方法中不能使用成员属性
制作工具类
制作工具类
ArrayTools
制作帮助文档(API)
javadoc -d 目录 -author -version ArrayTool.java
工具类:将一些常用的方法,该方法可以被static修饰,放置一个类中进行保存,之后如果使用相关的方法,可以直接使用类名.方法进行调用
代码块
构造代码块
构造代码块是在构造方法之前执行
形式:在类中方法外使用{}进行定义
在每个对象被创建时调用,类在调用时并不会执行构造代码块
静态代码块
会随着类的加载而被执行
形式:在类中方法外使用static{}进行定义
当类被加载时,会默认先执行其静态代码块,智慧再去执行构造代码块,并且静态代码块只会执行一次
原因:当程序运行时,会将当前的.class文件加载到方法区中的class文件区,在整个过程中,只需要加载一次,所以静态代码块只会执行一次
局部代码块
当类中的方法被执行时,才会执行
形式:在方法中使用{}进行定义
作用:限定变量的作用范围,当在{}内定义的变量,不能在{}外进行使用,当{}代码块执行完成,该变量会被销毁,该方式用于代码优化,当{}执行完成,变量的内存空间也会被释放
同步代码块
多线程阶段在讲
为什么会需要有如上类型的代码块?
当使用工具类时,可以直接使用类名,静态方法进行调用,此时对于一些静态变量如果需要做初始化赋值,此时没有一个比较不错的方式,于是可以定义一个静态代码块,当类被加载时,可以执行相对应的代码块内容 于是在该代码块中就可以实现静态变量的初始化
当对象被构建时,在构造方法执行之前可能需要做一些环境的初始化工资,于是可以定义构造代码块,比如要创建对象操作Mysql中的,此时可以直接使用构造代码块,从给定的配置信息中读取连接信息,进行链接,当构造代码块执行完成后,可以获取一个对象
静态代码块、构造代码块、构造函数执行顺序
父类静态代码块 > 子类静态代码块 > main()方法 > 父类(构造)代码块 > 父类构造器(方法)> 子类(构造)代码块 > 子类构造器(方法)
形式参数问题
基本类型作为形式参数 : 基本类型作为参数传递的时候是在一个方法栈中开辟了一块新内存,拷贝了原来的数据值,所以无论我们如何修改,原来的数据值不会受到任何影响。
引用类型作为形式参数 : 首先我们要知道引用的数据存储在栈内存中,而引用指向的对象存储在堆内存中。
当引用作为方法参数传递给方法的时候,是将引用的值拷贝一份给另一个引用,但引用指向的都是同一个堆内存,所以进行的修改操作同样有效。
引用类型中,形参能够改变实参的值,或者一个引用能够改变另一个引用的值,仅仅是因为他们栈内存中存储的值相同,但这个值是随时可以修改的。
实例:
public class Practice2 {public static void main(String[] args) {// TODO Auto-generated method stubint a = 5;System.out.println(a);//5change(a);System.out.println(a);//5}public static void change(int b) {b = 500;}
}
----------------------------------
public class Practice {static A a = new A(10);public static void main(String[] args) {// TODO Auto-generated method stubPractice practice = new Practice();System.out.println(practice.a.intData);//10change(practice.a);System.out.println(practice.a.intData);//500}public static void change(A aa) {aa.intData = 500;System.out.println(aa.intData);//500}
}class A{int intData;public A(int intData) {this.intData = intData;}}
继承
继承关系:
语法:
class 子类名 extends 父类名 {}
说明:
① 对于继承关系,子类可以获取到父类中的所有属性
② 对于子类可以存在有自身独特的属性
③ 对于子类也可以继承父类中的方法
④ 当子类中对于父类继承过来的方法存在有重名,那么在对象调用时,根据就近原则调用自身的成员方法
⑤ 对于一个父类可以存在有多个子类
⑥ Java中不能实现多继承,在子类中只能存在单个的父类
⑦ 对于现实逻辑来看,在类中会存在有多个类之间的关系,如何定义多个类关系?
Java 虽然不支持多继承,但是支持多层继承(单继承)
⑧ 继承关系中的构造方法:
在创建对象时,默认情况下,会先去通过extends关键字找到父类中的构造方法,并执行其构造方法,之后
再去执行子类中的构造方法, => 要想获取子类对象先创建父类对象
当构造代码块和构造方法都存在时,那么会先执行父类中的构造代码块和构造方法再执行子类中的构造代码块和构造方法
静态代码块会先执行(将继承关系中所有的类加载完成) -> 构造代码块
继承的好处
提高了代码的复用性
多个类相同的成员可以放到同一个类中
提高了代码的维护性
如果功能的代码需要修改,修改一处即可
让类与类之间产生了关系,是多态的前提
其实这也是继承的一个弊端:类的耦合性很强
耦合性:
表示代码之间的关系,该关系好处是减少代码量,坏处是代码修改时,
会牵扯过多的子类,导致开发复杂,难度较大
注意:
子类只能继承父类所有非私有的成员(成员方法和成员变量)
什么时候使用继承关系?
当两个类存在有is a的关系 而不是一个类中有少量的方法需要获取时继承
出现同名的变量:
① 变量再方法中有就近原则
② 当子类中有父类同名的变量,那么创建子类对象时,对应变量赋值,会根据就近原则直接赋予给当前对象
查看继承过程中构造方法的相关问题:
① 当子类对象被构建时,需要先创建父类中的对象,才能有子类对象
问题:什么时候创建,如何调用的?
当类被加载后,再去调用构造方法 加载过程是先加载父类,之后再加载子类的类对象
当创建子类对象时,默认先去寻找父类中的无参构造,再由父类无参构造执行,执行完成后,再去执行子类构造
② 当父类中无参构造被有参构造覆盖时,此时不能通过默认的方式创建对象,同时编码不会立即报错,该错误是编译时期的错误
③ 当需要使用父类中的构造方法时,可以使用super关键字,该关键字和this关键字形式一样,this代表当前对象,而super代表父类对象
④ 当子类的构造方法中需要使用super关键字,那么必须要放在第一行 为啥?
当子类运行构造方法时,必须要根据继承关系,先运行父类中的构造方法,此时必须要将super放在构造方法中的第一行
拓展:对于this来说 也可以通过this(参数列表)调用 无参构造或有参构造
类的继承格式
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
super关键字:
(1)super能出现在实例方法和构造方法中。
(2)super的语法是“super.”和“super()”。
(3) super不能出现在静态方法中。
(4) super大部分情况下是可以省略的。
(5)super.什么时候不能省略呢?
别急,我们想一下this指向的是什么,是当前对象自己。super和this类似,它指向了当前对象自己的父类型特征(也就是继承过来的那些东西)。
super和this区别是:this可以看做一个引用变量,保存了该对象的地址,是当前对象整体,而super代表的是父类型特征,是子类局部的一些东西,这些继承过来的东西已经在子类里面了,你可以输出整体this,但不能输出父类型特征super。因为super指向的东西不是一个整体,没法打印输出。
System.out.println(this); //输出this.toString()的值
System.out.println(super); //编译报错,需要'.'
当在子类对象中,子类想访问父类的东西,可以使用“super.”的方式访问。例如:方法覆盖后,子类内部虽然重写了父类的方法,但子类也想使用一下父类的被覆盖的方法,此时可以使用“super.”的方式。当子类中出现和父类一样的属性或者方法,此时,你要想去调用父类的那个属性或者方法,此时“super.”不能省略。
this和super都只能在对象内部使用。
this代表当前对象本身,super代表当前对象的父类型特征。
“this.”是一个实例对象内部为了区分实例变量和局部变量。
而“super.”是一个实例对象为了区分是子类的成员还是父类的成员。
父类有,子类也有,子类想访问父类的,“super.”不能省略。
(6)super()只能出现在构造方法的第一行,通过当前的构造方法去调用“父类”中的对应的构造方法,目的是:创建子类对象时,先初始化父类型特征。
用通俗的话来讲,要想有儿子,得先有父亲。
我问你,当构造方法第一行有"this()"时,你还能手动添加“super()”吗?显然不行,因为“this()”也只能出现在第一行,你不能在它前面写任何代码。所以我们又得出一个结论:构造方法中“this()”和“super()”不能同时出现,也就是“this()”和“super()”都只能出现在构造方法的第一行。上面谈的都是无参数的“super”方法,我们也可以在构造方法的第一行使用有参数的“super(父类构造函数的参数列表)”,但值得注意的是,当子类构造方法执行有参数的“super(参数列表)”方法,你得确保父类中也有对应的有参数构造方法,不然会编译报错。同样我要提醒一下,当子类构造方法的第一行执行super()无参数方法,那么父类中一定要有无参数构造方法,有的人可能会在父类中写了有参数的构造方法,却忽略了写无参数构造方法,那么在子类构造方法内就会报错,因为当你在一个类中写了有参数的构造方法时,无参数构造方法就会不存在,你需要自己补上无参数的构造方法,这是一个良好的编程习惯。无论你子类构造方法有没有“this()”和“super()”方法,实例化子类对象一定一定会执行对应的父类构造方法,即不管实例化了一个怎样的孩子,它一定会先实例化一个对应的父亲。
先复习一下this关键字的使用。
(1)this能出现在实例方法和构造方法中;
(2)this的语法是“this.”和“this()”;
(3)this不能出现在静态方法中;
(4)this大部分情况下是可以省略的;
(5)this.什么时候不能省略呢?
在区分局部变量和实例变量时不能省略。例如:
Public void setName(String name){this.name = name;
}
final关键字
final关键字是最终的意思,可以修饰类,成员变量,成员方法。
修饰类,类不能被继承
修饰变量,变量就变成了常量,只能被赋值一次
修饰方法,方法能被继承,方法不能被重写
final关键字面试题
final修饰局部变量
在方法内部,该变量不可以被改变
在方法声明上,分别演示基本类型和引用类型作为参数的情况
基本类型,是值不能被改变
引用类型,是地址值不能被改变
final修饰变量的初始化时机
在对象构造完毕前即可
需求:当前定义的类为最终类,不能被其他子类进行继承...在类名中使用 final修饰*//*需求:在某些类中,某些成员方法,不能被子类所修改*//*总结:对于final可以修饰 类、成员属性、成员方法修饰类:当前类不能被继承形式:final class 类名 {}修饰成员属性:当前属性为一个常量形式: final 数据类型 常量名注意:对于修饰的是一个引用类型,那么地址值不能改变,但是地址所指向的位置空间数据可以改变修饰成员方法:当前方法可以被继承,但是不能被修改形式: public final 返回值类型 方法名() {}*/
构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
实例
package com.test.demo11;public class Test4 {public static void main(String[] args) {System.out.println("------SubClass 类继承------");SubClass sc1 = new SubClass();SubClass sc2 = new SubClass(100);System.out.println("------SubClass2 类继承------");SubClass2 sc3 = new SubClass2();SubClass2 sc4 = new SubClass2(200);}
}
class SuperClass{private int n;SuperClass(){System.out.println("SuperClass()");}SuperClass(int n){System.out.println("SuperClass(int n)");this.n=n;}
}
class SubClass extends SuperClass{private int n;SubClass(){//自动调用父类的无参构造器System.out.println("SubClass");}public SubClass(int n){super(300);//调用父类中带有参数的构造器System.out.println("SubClass(int n):"+n);this.n=n;}
}
class SubClass2 extends SuperClass{private int n;SubClass2(){super(300);//调用父类中有参构造System.out.println("SubClass2");}public SubClass2(int n){System.out.println("SubClass2(int n):"+n);this.n=n;}
}
结果:
------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200
重写与重载
重写(Override)
重写的前提是继承,只有存在继承关系的类之间才存在重写。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
注意事项
重写方法的不能比被重写的方法有更严格的访问限制
相同的方法名,相同的参数列表,相同的返回值
重写方法不能抛出比被重写方法声明更广的异常,可以抛出范围更小的或不抛出异常
不能重写被final修饰的方法
如果一个方法不能被继承,则不能被重写。例如:构造器不能被继承,所以不能被重写。或者是父类声明为private的方法
子类不能重写父类的构造方法,不能重写static修饰的方法。
方法的重写规则
参数列表与被重写方法的参数列表必须完全相同。
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
父类的成员方法只能被它的子类重写。
声明为 final 的方法不能被重写。
声明为 static 的方法不能被重写,但是能够被再次声明。【父类静态方法,子类也必须通过静态方法进行重写。(其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中我会讲解)】
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
构造方法不能被重写。
如果不能继承一个方法,则不能重写这个方法。
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。和返回类型无关。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
如果方法有明确的返回值,一定要有return带回一个值
重写与重载之间的区别
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
多态
多态存在的三个必要条件
-
继承
-
重写
-
父类引用指向子类对象
在逻辑关系上,对象可以是由多种描述方式,但是前提是对象有对应的继承关系多态:将子类对象赋予给父类变量 -> 有父类引用指向子类对象前提:有继承关系 多态成员访问:成员变量编译看左边,运行看左边成员方法编译看左边,运行看右边静态方法编译看左边,运行看左边 多态中的转型:向上转型 (多态的形式)从子到父父类引用指向子类对象向下转型从父到子父类引用转为子类对象注意事项:当进行做强制类型转换时,在运行时,会校验当前地址的对象是否和变量的类型存在有继承关系 实例:Per per = new Stu(); // 将子类的对象赋予给父类变量 从子到父Stu stu1 = (Stu) per; // 向下转换 将父类变量强制类型转换为子类变量//上面两行同时有才能像下转型Per tea = new Tea(); // 向上转型/*注意: tea中的内存地址实际上是指向一个教师对象,现在通过强制转换将 教师对象地址赋予给 Stu stu2 学生变量*/Stu stu2 = (Stu) tea; // 强制转换 有类型转换问题
Per per = new Stu(); // 将子类的对象赋予给父类变量 从子到父//从父到子 Per per = new Stu(); Stu stu1 = (Stu) per;
向上转型和向下转型
对象的向上转型:父类 父类对象 = 子类实例
对象的向下转型:子类 子类对象 = (子类)父类实例
示例一
父类强制转子类Father f = new Father();
Son s = (Son)f;//出错 ClassCastException分析:
创建一个父类的实例,想要强制把父类对象转换成子类的,不行!
通俗的想,父亲永远不可能转换成儿子。因为有空白,假如转换了,那么上幼儿园、哺乳,这些儿子的特有属性父亲是没有的,强转就出现了ClassCastException示例二
“假的”父类强制转子类Father f = new Son();
Son s = (Son)f;//可以分析:
父类对象引用着一个子类实例。
Son类特有的属性暂时不能通过 f 来操作,因为Father类没有Son类(子类)的特有属性。
接着创建子类对象 s,它引用的是父类对象 f 强制转换来的对象(其实就是个装爹的Son,把他强制转回了Son),这时就可以通过 s 来操作子类的特有属性了。通俗的说就是儿子装爹,终究是儿子,本质没变,还是可以把他强制转回儿子的。示例三
子类强制转父类Son s = new Son();
Father f = (Father)s;//可以分析:
子类转换成父类,只是子类对象的特有属性无法利用 f 操作,f 可以操作其非特有的属性(由父类继承而来的属性)。也就是说,儿子可以执行吃饭和睡觉的方法,父亲也有,因为这就是从父亲继承的,但是让父亲执行上小学的方法就不行,因为这是儿子的‘‘特有属性’’。通俗的说,儿子和爹的共同点——“都是人”,儿子作为人的基本属性是从父亲继承过来的,儿子转换成了父亲,只是失去了原本自己比父亲多出来的那些特有属性。
抽象类
抽象类
抽象类的定义规则:
抽象类和抽象方法都必须用abstract关键字来修饰;
抽象类不能被实例化,也就是不能用new关键字去产生对象;
抽象方法只需声明,而不需实现;
含有抽象方法的类必须被声明为抽象类,抽象类的子类必须复写所有的抽象方法后才能被实例化,否则这个子类还是个抽象类
抽象类的定义格式:abstract class 类名称 // 定义抽象类{声明数据成员;访问权限 返回值的数据类型 方法名称(参数...)//定义一般方法{...}abstract 返回值的数据类型 方法名称(参数...);//定义抽象方法,在抽象方法里,没有定义方法体}
package com.shujia.day07;import com.shujia.day07.animal.Animal;import com.shujia.day07.animal.Cat;import com.shujia.day07.animal.Dog;import com.shujia.day07.father.GrandFather;import com.shujia.day07.father.Son;public class Demo02Abstract {public static void main(String[] args) {/*什么是抽象:① 从逻辑角度看 是表示一个概念性的东西② 从代码角度来看 表示方法时 该方法只有具体的方法格式,而没有实现的代码块public void eat() => 抽象的方法抽象类:① 从逻辑角度看 用于表示一个 物种的抽象种类 或者是对某个概念总和的抽象比如:对于猫和狗都是动物,那么 猫类 狗类 动物类中 ,动物类是抽象类② 从代码角度来看,包含有抽象方法的类称为抽象类对于抽象方法,需要和其他方法进行区分,于是可以添加abstract进行标记对于包含抽象方法的类,需要对其进行标记 可以在类定义时添加 abstract 进行标记定义格式:public abstract class 抽象类名{public abstract 返回值类型 方法名(参数列表); // 没有方法体}单独的抽象类,并没有实际意义,因为抽象方法,没有具体的实现,所以可以构建类和类之间的关系——> 使用继承于是可以使用 public class 类A extends 抽象类B => 由于 class 类A 是一个非抽象类=> 根据继承关系 得到抽象类B中的抽象方法 => 但是类A不是抽象类,不能包含抽象的方法=> 于是可以通过重写的方式,对其中的抽象方法进行重写,并定义其具体的代码块注意:1.抽象类不能创建其对象2.抽象类的子类对象,可以指向抽象类的变量 (多态) 父类引用指向子类对象从逻辑上看,猫和狗都是属于动物中的一种,所以可以使用动物来描述猫和狗3.抽象类中可以有非抽象的方法:(逻辑理解) 对于 爷爷类 父亲类, 对于爷爷类来说,会有一些未完成的理想,但是同时也有一些资产对于父亲类来说,继承了爷爷类的资产和理想 => 对于爷爷类,当爷爷去世,那么该类就是一个抽象类4.抽象类的子类可以是抽象的,也可以是非抽象的对于抽象子类,不需要去实现其抽象方法5.类型之间可以存在传递关系爷 父 子 => 对于子的类的对象 可以使用爷类类型 变量 来接收6.对于抽象类中的变量:6.1 可以被子类继承6.2 变量不能使用abstract进行修饰6.3 对于static修饰的变量,可以直接使用类名进行调用7.对于继承关系中,抽象类继承了另一个抽象父类,此时抽象类可以实现父类中的抽象方法也可以不实现其抽象方法8.在抽象类的编译文件中,通过反编译可以看到其构造方法,为什么有?其作用是什么?在继承关系中,子类如果要创建对象,那么需要调用自身的构造方法,但是创建之前,先去执行父类中的构造方法之后再去,执行后续的代码,所以父类虽然是一个抽象类,但是必须要有其构造方法9.在子类中,可以通过super()显示调用父抽象方法中的构造方法注意:当编写代码时,出现有红色波浪 可以使用 alt + enter键 选择idea提供的错误解决方案一个类如果没有抽象方法,可不可以定义为抽象类?如果可以,有什么意义?可以,该抽象类,可以用于类之间的逻辑划分,抽象类表示的是一个抽象的概念,并没有具体的实例abstract 不能和哪些关键字共存private 冲突: abstract修饰的方法是一个抽象方法,对于抽象方法来说要使其有意义,那么必须通过继承来进行实现如果使用private修饰,那么子类无法通过继承来获取到final 冲突: abstract修饰的方法是一个抽象方法,对于抽象方法来说要使其有意义,那么必须通过继承来进行实现对于继承时,如果使用 final的方法 不能对方法进行重写操作static 没有意义: static可以直接通过类进行调用,但是当前抽象类中抽象方法,没有具体的实现代码块*/// Animal animal = new Animal(); // 抽象类中编译时,虽然有构造方法,但是在该方式中不能创建单独的对象Cat cat = new Cat();cat.eat();cat.sleep();Animal animal1 = new Cat();animal1.eat();Dog dog = new Dog();dog.eat();Animal animal2 = new Dog();animal2.eat();// Father father = new Father();// long money = father.getMoney();// System.out.println("现在有"+money+"钱");// father.haveDream();GrandFather son = new Son();long money = son.getMoney();System.out.println("现在有"+money+"钱");son.haveDream();System.out.println(son.sign);System.out.println(GrandFather.NAME);// GrandFather.getMoney(); // static修饰的类可以被抽象类所调用// Father father = new Father();}}
接口
接口的定义
接口(interface)是Java所提供的另一种重要技术,它的结构和抽象类非常相似,也具有数据成员与抽象方法,但它与抽象类又有以下两点不同:
接口里的数据成员必须初始化,且数据成员均为常量;
接口里的方法必须全部声明为abstract,也就是说,接口不能像抽象类一样保有一般的方法,而必须全部是“抽象方法”。
接口定义的语法如下:interface 接口名称 // 定义抽象类
{
final 数据类型 成员名称 = 常量; //数据成员必须赋初值
abstract 返回值的数据类型 方法名称(参数...);
//抽象方法,注意在抽象方法里,没有定义方法主体
}
接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值将不能再更改,方法也必须是“抽象方法”。也正因为方法必须是抽象方法,而没有一般的方法,所以抽象方法声明的关键字abstract是可以省略的。
**相同的情况也发生在数据成员身上,因数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字final也可省略。**事实上只要记得:
接口里的“抽象方法”只要做声明即可,而不用定义其处理的方式;
数据成员必须赋初值。
接口的实现
在Java中接口是用于实现多继承的一种机制,也是Java设计中最重要的一个环节,每一个由接口实现的类必须在类内部复写接口中的抽象方法,且可自由地使用接口中的常量。
既然接口里只有抽象方法,它只要声明而不用定义处理方式,于是自然可以联想到接口也没有办法像一般类一样,再用它来创建对象。利用接口打造新的类的过程,称之为接口的实现(implementation)。
接口实现的语法:class 类名称 implements 接口A,接口B //接口的实现
{
...
}
接口的扩展
接口是Java实现多继承的一种机制,一个类只能继承一个父类,但如果需要一个类继承多个抽象方法的话,就明显无法实现,所以就出现了接口的概念。一个类只可以继承一个父类,但却可以实现多个接口。
接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口,派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可加入新的成员以满足实际的需要。
同样的,接口的扩展(或继承)也是通过关键字extends来实现的。有趣的是,一个接口可以继承多个接口,这点与类的继承有所不同。
接口扩展的语法:interface 子接口名称 extends 父接口1,父接口2...
{
...
}
package com.shujia.day07;import com.shujia.day07.teacher.Sing;
import com.shujia.day07.teacher.TeacherInterface;public class Demo05Interface {public static void main(String[] args) {/*接口:接口的意义实际上是对当前的类所属功能进行拓展比如 电脑 通过 USB接口/ HDMI接口 外接其他设备,使其具有相对应的一些功能接口在Java中的意义?对于有些抽象类,其子类中并不是所有的方法都是抽象类所共有的,需要通过一些其他的方式再添加一些功能到当前子类中比如:教师抽象类中定义的方法 是所有教师子类所都有的,但是部分教师都有自身所独特的技能教师A -> 唱歌教师B -> 唱歌包装唱歌为一个接口,每个教师唱歌的风格可能不一样,对应唱歌的方法有自己的实现形式教师C -> 乐器教师D -> 乐器包装乐器为一个接口,每个教师掌握的乐器可能不一样,对应乐器有自己的实现形式如何定义接口:形式:public interface 类名 {}使用单独的关键字对其进行描述,为什么接口不能是一个 class 而必须是单独的一种类型?注意:接口是功能性的拓展,是对当前的类给一些独特的 方法规范注意:① 对于接口中定义的方法,只是一个规范,不能存在有具体的方法体,具体的方法需要在子类中进行实现② 接口不能构建其对象,所以需要借助类来构建,并对其进行调用③ 对于接口要通过其子实现类来进行实现其抽象方法,使用关键字 implements④ 对于接口中可以定义其属性,但是需要对其属性进行赋予初始值接口中的属性,一般情况下,如果要定义,那么经常是以常量的形式存在,比如一些配置接口,将配置信息设置成一个常量形式⑤ 接口和抽象类的区别方法上的区别:抽象类:既可以定义抽象方法也可以定义非抽象方法接口: 只能定义抽象方法存在的意义不同:抽象类: 对于一个概念进行做抽象,将其相关的属性和方法形成一个类接口: 对当前类的功能进行拓展,相当于是电脑的一个外接接口⑥ 从代码设计角度为什么需要接口?1. 因为如果使用抽象类来设计接口,那么根据Java的定义原则,只能存在有单继承或者多层继承的关系不支持多继承,于是需要单独设计一个 interface2. 在实际开发过程中,对于一个类,可能需要实现多个不同的接口,而抽象类无法提供⑦ 一个类可以实现多个接口,其格式为:class 类名 implements 接口1,接口2⑧ 接口可以存在有继承关系,并且接口存在有多继承关系,为什么?对于接口来说,继承以后,不能对其方法进行重写,那么就不会造成多继承中对于相同方法的不同实现*/// Sing sing = new Sing(); // 接口不能构建其对象TeacherInterface teacherInterface = new TeacherInterface();teacherInterface.sing();
// System.out.println(teacherInterface.singName);System.out.println(teacherInterface.SING_NAME);teacherInterface.skill();}
}
实参和形参
形式参数在方法的参数列表中定义的参数称为形式参数基本类型:一般情况下是传递具体的数值给形参,对于形参的计算,并不会改变原来的实参引用类型:一般情况下是传递内存地址给形参,如果对内存地址的变量进行修改,那么会改变原来实参的数值实参:除形式参数以外的其他参数,在一般情况下,在方法内进行定义的
返回值
返回值类型基本类型引用类型
封装
什么是封装
在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
封装概述
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:
隐藏实现细节,提供公共的访问方式
提高了代码的复用性
提高安全性。
封装原则:
将不需要对外提供的内容都隐藏起来。
把属性隐藏,提供公共方法对其访问。
匿名对象
匿名对象:就是没有名字的对象。
是对象的一种简化表示形式
匿名对象的两种使用情况
对象调用方法仅仅一次的时候
作为实际参数传递
包
包:
在创建类时,会默认给当前类中第一行代码添加 package 路径 用于表示当前类所在的位置
该位置的作用是,在编译时,会将当前的.class文件放入指定路径中
路径是使用.进行分割
类路径: com.shujia.day08.teacher.Teacher 可以使用idea选中当前类 右键 选择copy Reference
包的作用
1、把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
2、如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
3、包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。
Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
使用关键字 import 类路径
什么时候需要使用import导入?
当所需的类和当前类不在一个包路径下时,需要使用import导入
对于Java中的java.lang路径下的类,并不需要对其进行导入操作
在同一个包下,所导入的类有多个时,那么此时可以使用 * 来替代
比如:import com.shujia.day08.teacher.*;
注意:该方式,只能导入一层包下的所有类,不能进行迭代导入;
当导入的多个类出现重复时:(了解)
① 使用非*形式,类名称相同会报错
② 使用*形式,直接导入类名称的方式优先级会比*更高
注意事项:
package语句必须是程序的第一条可执行的代码
权限修饰符
在Java中,有四种主要的访问修饰符(权限修饰符)用于控制类、方法和变量的访问级别。这些访问修饰符分别是:public
、protected
、默认(package-private,默认即不写修饰符)和private
。下面是这些访问修饰符的作用范围:
- public:
- 在任何地方都可以访问,即对所有类可见。
- 示例:类、方法、变量被声明为
public
。
- protected:
- 同一类中可以访问。
- 同一包中的子类可以访问。
- 不同包中的子类可以访问(前提是子类引用的对象是子类的实例)。
- 示例:方法、变量被声明为
protected
。
- 默认(package-private,默认即不写修饰符):
- 同一类中可以访问。
- 同一包中的子类可以访问。
- 示例:类、方法、变量未使用任何修饰符,默认为包内可见。
- private:
- 同一类中可以访问。
- 示例:方法、变量被声明为
private
。
内部类
分类:成员内部类、局部内部类、静态内部类、匿名内部类
注意:
(1)、内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。
(2)、内部类不能用普通的方式访问。
(3)、内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量 。
(4)、外部类不能直接访问内部类的的成员,但可以通过内部类对象来访问
成员内部类
即在一个类中直接定义的内部类,成员内部类与普通类的成员没什么区别,可以与普通成员一样进行修饰和限制。成员内部类不能含有static的变量和方法。
package com.test.demo10;public class Outer {private static int number = 100;private int j =20;private String name ="java";public static void outer_funOne(){System.out.println("外部类的静态方法");}public void outer_funTwo(){System.out.println("外部类的普通方法");}//内部类class Demo{//static int demos=22;//内部类不能定义静态变量int j = 50;//内部类和外部类的实例变量可以共存//成员内部类的方法定义public void demo_funOne(){//内部类中访问内部类自己的变量可以直接使用变量名也可以用this.jSystem.out.println(j);//内部类中访问外部类的成员变量语法:外部类类名.this.变量名System.out.println("内部类访问外部类变量"+Outer.this.j);//如果内部类中没有与外部类中相同的变量,也可以直接使用变量名System.out.println(name);//内部类调用外部类方法outer_funOne();//静态方法outer_funTwo();//非静态方法}}public static void outer_funThree(){//外部类静态方法访问成员内部类//先建立外部类对象Outer out = new Outer();//根据外部类建立内部类对象Demo demo =out.new Demo();//访问内部类方法demo.demo_funOne();//访问内部类成员System.out.println("内部类的成员:"+demo.j);}//外部类成员方法访问成员内部类public void outer_funFirst(){Demo outs =new Outer().new Demo();outs.demo_funOne();System.out.println(outs.j);}public static void main(String[] args) {//调用内部类的方法Outer.Demo demo1 = new Outer().new Demo();demo1.demo_funOne();System.out.println(demo1.j);Outer out =new Outer();System.out.println(out.j);out.outer_funFirst();Outer.outer_funThree();//静态方法用类名直接调用}
}
局部内部类
在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能有访问说明符,因为它不是外围类的一部分,但是它可以访问当前代码块内的常量,和此外围类所有的成员。
需要注意的是:
(1)、局部内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
(2)、局部内部类对象不能使用该内部类所在方法的非final局部变量。
package com.test.demo10;public class Outers {private static int number =100;private int j =20;private String name = "java";//定义外部类方法public void outer_funOne(int k){final int number = 100;int j =50;//方法内部的类(局部内部类)class Demo{public Demo(int k){demo_funOne(k);}final int number =300;//可以定义与外部类同名的变量//static int j =10;//不可以定义静态变量//内部类方法public void demo_funOne(int k){System.out.println("内部类方法:demo_funOne");//访问外部类的变量,如果没有与内部类同名的变量,可以直接用变量名System.out.println(name);//访问外部类与内部类同名的变量(内部与外部都能访问)System.out.println("我是内部的:"+this.number);System.out.println("我是外部的"+Outers.number);System.out.println("内部类方法传入的参数是:"+k);}}Demo demo = new Demo(k);//外部类要创建内部类}public static void main(String[] args) {//访问内部类必须要先有外部类对象Outers out =new Outers();out.outer_funOne(15);}
}
静态内部类(嵌套类)
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:
1. 要创建嵌套类的对象,并不需要其外围类的对象。
2. 不能从嵌套类的对象中访问非静态的外围类对象。
package com.test.demo10;public class Outer1 {private static int number =100;private int j =20;private String name="java";public static void outer1_funOne(){System.out.println("外部类静态方法:outer_funOne");}public void outer1_funTwo(){System.out.println("外部类普通方法");}//静态内部类可以用public,protected,private修饰private static class Demo{static int j =100;//静态内部类可以定义静态和非静态的变量String name ="C";//静态内部类的静态方法static void demo_funOne(){//静态内部类只能访问外部类的静态成员(静态变量、静态方法)System.out.println("静态内部类访问外部类静态变量:"+number);outer1_funOne();//访问外部类静态方法}//静态内部类非静态方法void demo_funTwo(){}}public void outer_funThree(){//外部类访问内部类静态成员System.out.println(Demo.j);//访问静态方法Demo.demo_funOne();//访问静态内部类的非静态成员和方法,需要实例化内部类Demo demo=new Demo();System.out.println(demo.name);demo.demo_funTwo();}public static void main(String[] args) {new Outer1().outer_funThree();}
}
匿名内部类★★★
简单地说:匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:
1、只用到类的一个实例。
2、类在定义后马上用到。
3、类非常小(推荐是在4行代码以下)
4、给类命名并不会导致你的代码更容易被理解。
在使用匿名内部类时,要记住以下几个原则:
1、 匿名内部类不能有构造方法。
2、 匿名内部类不能定义任何静态成员、方法和类。
3、 匿名内部类不能是public,protected,private,static。
4、 只能创建匿名内部类的一个实例。
5、 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
6、 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
package com.test.demo10;public class HelloWorldAnonymousClasses {//包含两个方法的HelloWorld接口interface HelloWorld{public void greet();public void greetSomeone(String someone);}public void sayHello(){//1.局部类EnglishGreeting实现了HelloWorld接口class EnglishGreeting implements HelloWorld{String name="world";@Overridepublic void greet() {greetSomeone("world");}@Overridepublic void greetSomeone(String someone) {name =someone;System.out.println("Hello "+name+" "+someone);}}HelloWorld englishGreeting=new EnglishGreeting();//2.匿名类实现接口HelloWorld frenchGreeting =new HelloWorld() {String name = "mundo";@Overridepublic void greet() {greetSomeone("tout is monde");}@Overridepublic void greetSomeone(String someone) {name =someone;System.out.println(" 1"+name);}};englishGreeting.greet();frenchGreeting.greetSomeone("Fred");frenchGreeting.greet();}public static void main(String[] args) {HelloWorldAnonymousClasses myapp=new HelloWorldAnonymousClasses();myapp.sayHello();}}该例中用局部类来初始化变量englishGreeting,用匿类来初始化变量frenchGreeting和spanishGreeting,两种实现之间有明显的区别:1)局部类EnglishGreetin继承HelloWorld接口,有自己的类名,定义完成之后需要再用new关键字实例化才可以使用;2)frenchGreeting、spanishGreeting在定义的时候就实例化了,定义完了就可以直接使用;3)匿名类是一个表达式,因此在定义的最后用分号";"结束。
匿名内部类的语法
如上文所述,匿名类是一个表达式,匿名类的语法就类似于调用一个类的构建函数(new HelloWorld()),除些之外,还包含了一个代码块,在代码块中完成类的定义,见以下两个实例:
案例一,实现接口的匿名类:
HelloWorld frenchGreeting = new HelloWorld() {String name = "tout le monde";public void greet() {greetSomeone("tout le monde");}public void greetSomeone(String someone) {name = someone;System.out.println("Salut " + name);}};
案例二,匿名子类(继承父类):
package com.test.demo10;public class AnimalTest {private final String ANIMAL = "动物";public void accessTest() {System.out.println("匿名内部类访问其外部类方法");}class Animal {private String name;public Animal(String name) {this.name = name;}public void printAnimalName() {System.out.println(bird.name);}}// 鸟类,匿名子类,继承自Animal类,可以覆写父类方法Animal bird = new Animal("布谷鸟") {@Overridepublic void printAnimalName() {accessTest(); // 访问外部类成员System.out.println(ANIMAL); // 访问外部类final修饰的变量super.printAnimalName();}};public void print() {bird.printAnimalName();}public static void main(String[] args) {AnimalTest animalTest = new AnimalTest();animalTest.print();}
}
从以上两个实例中可知,匿名类表达式包含以下内部分:
-
操作符:new;
-
一个要实现的接口或要继承的类,案例一中的匿名类实现了HellowWorld接口,案例二中的匿名内部类继承了Animal父类;
-
一对括号,如果是匿名子类,与实例化普通类的语法类似,如果有构造参数,要带上构造参数;如果是实现一个接口,只需要一对空括号即可;
-
一段被"{}"括起来类声明主体;
-
末尾的";"号(因为匿名类的声明是一个表达式,是语句的一部分,因此要以分号结尾)
访问作用域内的局部变量、定义和访问匿名内部类成员
匿名内部类与局部类对作用域内的变量拥有相同的的访问权限。
(1)、匿名内部类可以访问外部内的所有成员;
(2)、匿名内部类不能访问外部类未加final修饰的变量(注意:JDK1.8即使没有用final修饰也可以访问);
(3)、属性屏蔽,与内嵌类相同,匿名内部类定义的类型(如变量)会屏蔽其作用域范围内的其他同名类型(变量):
(4)、匿名内部类中不能定义静态属性、方法;
(5)、匿名内部类可以有常量属性(final修饰的属性);
(6)、匿名内部内中可以定义属性,如上面代码中的代码:private int x = 1;
(7)、匿名内部内中可以可以有额外的方法(父接口、类中没有的方法);
(8)、匿名内部内中可以定义内部类;
(9)、匿名内部内中可以对其他类进行实例化。
案例一,内嵌类的属性屏蔽:1 public class ShadowTest {2 3 public int x = 0;4 5 class FirstLevel {6 7 public int x = 1;8 9 void methodInFirstLevel(int x) {
10 System.out.println("x = " + x);
11 System.out.println("this.x = " + this.x);
12 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
13 }
14 }
15
16 public static void main(String... args) {
17 ShadowTest st = new ShadowTest();
18 ShadowTest.FirstLevel fl = st.new FirstLevel();
19 fl.methodInFirstLevel(23);
20 }
21 }
复制代码
输出结果为:x = 23
this.x = 1
ShadowTest.this.x = 0
这个实例中有三个变量x:1、ShadowTest类的成员变量;2、内部类FirstLevel的成员变量;3、内部类方法methodInFirstLevel的参数。methodInFirstLevel的参数x屏蔽了内部类FirstLevel的成员变量,因此,在该方法内部使用x时实际上是使用的是参数x,可以使用this关键字来指定引用是成员变量x:1 System.out.println("this.x = " + this.x); 利用类名来引用其成员变量拥有最高的优先级,不会被其他同名变量屏蔽,如:1 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x); 案例二,匿名内部类的属性屏蔽:1 public class ShadowTest {2 public int x = 0;3 4 interface FirstLevel {5 void methodInFirstLevel(int x);6 }7 8 FirstLevel firstLevel = new FirstLevel() {9
10 public int x = 1;
11
12 @Override
13 public void methodInFirstLevel(int x) {
14 System.out.println("x = " + x);
15 System.out.println("this.x = " + this.x);
16 System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
17 }
18 };
19
20 public static void main(String... args) {
21 ShadowTest st = new ShadowTest();
22 ShadowTest.FirstLevel fl = st.firstLevel;
23 fl.methodInFirstLevel(23);
24 }
25 }
复制代码
输出结果为:x = 23
this.x = 1
ShadowTest.this.x = 0
(4)、匿名内部类中不能定义静态属性、方法; 1 public class ShadowTest {2 public int x = 0;3 4 interface FirstLevel {5 void methodInFirstLevel(int x);6 }7 8 FirstLevel firstLevel = new FirstLevel() {9
10 public int x = 1;
11
12 public static String str = "Hello World"; // 编译报错
13
14 public static void aa() { // 编译报错
15 }
16
17 public static final String finalStr = "Hello World"; // 正常
18
19 public void extraMethod() { // 正常
20 // do something
21 }
22 };
23 }
(5)、匿名内部类可以有常量属性(final修饰的属性);(6)、匿名内部内中可以定义属性,如上面代码中的代码:private int x = 1;(7)、匿名内部内中可以可以有额外的方法(父接口、类中没有的方法);(8)、匿名内部内中可以定义内部类;(9)、匿名内部内中可以对其他类进行实例化。
异常
异常概述
异常就是程序在运行过程中出现的一些错误,我们通过面向对象的思想,把这些错误也用类来描述,那么一旦产生一个错误,即就是创建了某一个错误的对象,这个对象就是我们所说的异常对象。
1)IndexOutOfBoundsException:
ArrayIndexOutOfBoundsException
数组角标越界异常 角标不在数组范围内
StringfIndexOutOfBoundsException
字符串角标越界异常 角标不在字符串范围内
(2)NullPointerException空指针异常 对null调用其成员。
(3)ArithmeticException数学运算异常 非法的数学运算。
(4)ClassCastException类型转换异常 将类进行错误的强制转换。
(5)NumberFormatException数字格式化异常 将数字字符串进行解析。
(6)InputMismatchException输入不匹配异常 在输入时输入非法数据。
(7)ParseException时间解析异常 非法的时间格式。
(8)StackOverFlowError栈内存溢出异常 函数递归。
(9)OutOfMemoryError堆内存异常 数组空间开辟过大 程序中对象太多。
Java异常体系
Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。
Throwable中的方法
getMessage()
获取异常信息,返回字符串。
toString()
获取异常类名和异常信息,返回字符串。
printStackTrace()
获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
printStackTrace(PrintStream s)
通常用该方法将异常内容保存在日志文件中,以便查阅。
异常分类
Error
Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。
运行时异常
运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
处理编译时异常的更多知识
声明异常
在 Java 中,当前执行的语句必属于某个方法。Java 解释器调用 main 方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常的类型。这称为声明异常( declaring exception)。只对编译时异常进行声明,、告知方法的调用者有异常。
为了在方法中声明一个异常,就要在方法头中使用关键字 throws, 如下所示:
public void myMethodO throws IOException
关键字 throws 表明 myMethod 方法可齙会抛出异常 IOException。如果方法可能会抛出多个异常,就可以在关键字 throws 后添加一个用逗号分隔的异常列表:
public void myMethodO throws Exceptionl, Exception2,… ,ExceptionN
注意:如果方法没有在父类中声明异常,那么就不能在子类中对其进行继承来声明异常。
抛出异常
检测到错误的程序可以创建一个合适的异常类型的实例并抛出它,这就称为抛出一个异常(throwing an exception)。这里有一个例子,假如程序发现传递给方法的参数与方法的合约不符(例如,方法中的参数必须是非负的,但是传入的是一个负参数),这个程序就可以创建 IllegalArgumentException 的一个实例并抛出它,如下所示:
IllegalArgumentException ex = new II1egalArgumentException("Wrong Argument");
throw ex;
或者,大多数使用下面的语句:
throw new IllegalArgumentException("Wrong Argument");
注意:IllegalArgumentException 是 Java API 中的一个异常类。通常,JavaAPI 中的每个异常类至少有两个构造方法:一个无参构造方法和一个带可描述这个异常的 String 参数的构造方法。该参数称为异常消息(exceptionmessage), 它可以用 getMessage()获取。
提示:声明异常的关楗字是 throws, 抛出异常的关键字是 throw。
捕获异常
现在我们知道了如何声明一个异常以及如何抛出一个异常。当抛出一个异常时,可以在try-catch 块中捕获和处理它;
try-catch-finally
try语句块中:放的是可能出现问题的代码,尽量不要把不相关的代码放入到里面,否则会出现截断的问题。
try{codeAthrow ...codeB
}
如果throw这个地方一旦抛出异常 codeB就不会执行了 建议把codeB放到后面。
catch语句块中:放的是出现问题并捕获后,处理问题的代码code,如果问题在try语句块中没有出现 则catch中不会运行。
catch(Exception e) {code
}
finally语句块中:放的是不管问题异常是否产生 都要执行的代码code。
finally{code//关闭资源(IO 数据库 网络),结尾处理的一些工作
}
在捕获异常中return的执行顺序
try、catch、finally、return执行顺序超详解析(针对面试题)
有关try、catch、finally和return执行顺序的题目在面试题中可谓是频频出现。总结一下此类问题几种情况。
不管try中是否出现异常,finally块中的代码都会执行;
当try和catch中有return时,finally依然会执行;
finally是在return语句执行之后,返回之前执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,不管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以如果finally中没有return,即使对数据有操作也不会影响返回值,即如果finally中没有return,函数返回值是在finally执行前就已经确定了;
finally中如果包含return,那么程序将在这里返回,而不是try或catch中的return返回,返回值就不是try或catch中保存的返回值了。
注:
finally修改的基本类型是不影响 返回结果的。(传值)
修改list,map,自定义类等引用类型时,是影响返回结果的。(传址的)对象也是传址的
但是date类型经过测试是不影响的。
try{} catch(){}finally{} return;
按程序顺序运行,如果try中有异常,会执行catch中的代码块,有异常与否都会执行finally中的代码;最终返回。
try{ return; }catch(){} finally{} return;
1.先执行try块中return 语句(包括return语句中的表达式运算),但不返回;
2.执行finally语句中全部代码
3.最后执行try中return 返回
finally块之后的语句return不执行,因为程序在try中已经return。
示例:
package com.test.demo11;public class Test {public int add(int a,int b){try {return a+b;}catch (Exception e){System.out.println("catch语句块");}finally {System.out.println("finally语句块");}return 0;}public static void main(String[] args) {Test test = new Test();System.out.println("和是:"+test.add(9,34));}
}
结果:
finally语句块
和是:43
至于为什么不是:和是 finally块43的原因:
System.out.println(“和是:”+test.add(9,34)); 这是进行字符串拼接是一个整体,所以首先是进入add方法,进去之后先不运算result,而是输出finally块。finally语句块,这句话先打印到控制台中。打印完后返回来执行try中的return得到43,此时再将结果与和是:拼接,输出和是:43.所以最终输出finally语句块 和是:43。
try{} catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,最终都会执行finally中的代码块;
有异常:
执行catch中的语句和return中的表达式运算,但不返回
执行finally语句中全部代码,
最后执行catch块中return返回。 finally块后的return语句不再执行。
无异常:执行完try再finally再return…
示例:
有异常public class Test04 {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);int i=1/0;} catch (Exception e) {System.out.println(temp);return ++temp;} finally {++temp;System.out.println(temp);}return temp;}
}输出:1132先执行try中的打印temp=1;有异常,执行catch中的打印,然后执行return中的表达式运算,此时temp=2,并将结果保存在临时栈中,但不返回;执行finally中的语句,temp++,此时temp更新为3,同时打印,但因为finally中的操作不是return语句,所以不会去临时栈中的值,此时临时栈中的值仍然是2。finally中的执行完后,执行catch中的返回,即2。示例2:无异常public class Test04 {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);} catch (Exception e) {System.out.println(temp);return ++temp;} finally {++temp;System.out.println(temp);}return ++temp;}
}输出:123
try{ return; }catch(){} finally{return;}
执行try块中的代码,和return语句(包括return语句中的表达式运算),但不返回(try中return的表达式运算的结果放在临时栈);
再执行finally块,
执行finally块(和return中的表达式运算,并更新临时栈中的值),从这里返回。
此时finally块的return值,就是代码执行完后的值
try{} catch(){return;}finally{return;}
执行try中的语句块,
有无异常
有异常:程序执行catch块中return语句(包括return语句中的表达式运算,,并将结果保存到临时栈),但不返回;
无异常:直接执行下面的
再执行finally块,
执行finally块return中的表达式运算,并更新临时栈中的值,并从这里返回。
示例:
无异常public class Test04 {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);} catch (Exception e) {System.out.println(temp);return ++temp;} finally {System.out.println(temp);return ++temp;}}
}
输出:112有异常public class Test04 {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);int i=1/0;} catch (Exception e) {System.out.println(temp);return ++temp;} finally {System.out.println(temp);return ++temp;}}
}输出:1123
try{ return;}catch(){return;} finally{return;}
程序执行try块中return语句(包括return语句中的表达式运算),但不返回;
有异常:
执行catch块中的语句和rreturn语句中的表达式运算,但不返回,结果保存在临时栈;
再执行finally块
执行finally块,有return,更新临时栈的值,并从这里返回。
无异常:
直接执行finally块
执行finally块,有return,更新临时栈的值,并从这里返回。
示例:
无异常
package com.jc;public class Test04 {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);
// int i=1/0;return ++temp;} catch (Exception e) {System.out.println(temp);return ++temp;} finally {System.out.println(temp);return ++temp;}}
}输出:123有异常
package com.jc;public class Test04 {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);int i=1/0;return ++temp;} catch (Exception e) {System.out.println(temp);return ++temp;} finally {System.out.println(temp);return ++temp;}}
}
输出:1123
try{ return;}catch(){return;} finally{其他}
程序执行try块中return语句(包括return语句中的表达式运算),但不返回;
有异常:
执行catch块中return语句(包括return语句中的表达式运算),但不返回;
再执行finally块
无异常:
再执行finally块
执行finally块,有return,从这里返回。
示例:
public class Test {public static void main(String[] args) {System.out.println(test());}private static int test() {int temp = 1;try {System.out.println(temp);return ++temp;} catch (Exception e) {System.out.println(temp);return ++temp;} finally {++temp;System.out.println(temp);}}
}
输出结果为132执行顺序为:输出try里面的初始temp:1;
temp=2;
保存return里面temp的值:2;
执行finally的语句temp:3,输出temp:3;
返回try中的return语句,返回存在里面的temp的值:2;
输出temp:2。
finally代码块在return中间执行。return的值会被放入临时栈,然后执行finally代码块,如果finally中有return,会刷新临时栈的值,方法结束返回临时栈中的值。
小结
任何执行try 或者catch中的return语句之后,在返回之前,如果finally存在的话,都会先执行finally语句,
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
不管有没有异常,finally代码块(包括finally中return语句中的表达式运算)都会在return之前执行
多个return(中的表达式运算)是按顺序执行的,多个return执行了一个之后,后面的return就不会执行。不管return是在try、catch、finally还是之外。
泛型
什么是泛型
型,即“参数化类型”。我们最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。
本质:其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
例如:
ArrayList<E> objectName =new ArrayList<>();
E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型。
作用
**第一是泛化。**可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。
**第二是类型安全。**泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast Exception异常,如果使用泛型,则会在编译期就能发现该错误。
**第三是消除强制类型转换。**泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
**第四是向后兼容。**支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。
优点
1、类型安全
2、消除强制类型转换
3、更高的运行效率
4、潜在的性能收益
泛型分类 :泛型类、泛型接口、泛型方法
首先我们得先了解泛型字母的含义
字母 | 意义 |
---|---|
E-Element | 在集合中使用,因为集合中存放的是元素 |
T-Type | Java类 |
K - Key | 键 |
v - Value | 值 |
N- Number | 数值类型 |
泛型类
定义格式:
修饰符 class类名<代表泛型的变量>{}
例子:
/*** @Description: $定义一个含有泛型的类, 模拟Arraylist集合* 泛型是一个未知的数据类型,当我们不确定什么什么数据类型的时候,可以使用泛型* 泛型可以接牧任意的数据类型,可以使用Integer , String, student.. .* 创建对象的时候确定泛型的数据类型* @Author: dyq* @Date: $*/
public class GenericClass<E> {private E name;public E getName() {return name;}public void setName(E name) {this.name = name;}public static void main(String[] args) {//不写泛型默认为ObjectGenericClass gc =new GenericClass();gc.setName("我是Object");Object name = gc.getName();System.out.println(name);//可以通过创建对象时确定数据类型//泛型使用Integer类型GenericClass<Integer> Igc = new GenericClass<>();Igc.setName(1);Integer i = Igc.getName();System.out.println("我是Interger类型:"+i);//泛型使用String类型GenericClass<String> sgc = new GenericClass<>();sgc.setName("花花");String s = sgc.getName();System.out.println("我是String类型:"+s);}
}结果:
我是Object
我是Interger类型:1
我是String类型:花花
泛型方法
语法:
修饰符<泛型>返回值类型方法名(参数列表(使用泛型)){
方法体;
}
例子:
/*** @Description: 泛型方法$ 含有泛型的方法,在调用方法的时候确定泛型的数据类型,* 传递什么类型的参数,泛型是什么类型* @Author: dyq* @Date: $2021年2月5日*/
public class GenericMethod {//定义一个含有泛型的方法public <M> void method01(M m){System.out.println(m);}//定义一个含有泛型的静态方法public static <S> void method02(S s){System.out.println(s);}//测试含有泛型的主方法public static void main(String[] args) {GenericMethod m = new GenericMethod();m.method01(10);m.method01("你好");m.method01('男');m.method01(19);System.out.println("===============");m.method02("我是方法02");m.method02("ok!");m.method02(1);m.method02('女');}
}结果:
10
你好
男
19
===============
我是方法02
ok!
1
女
泛型接口
格式:
修饰符interface接口名<代表泛型的变量>{ }
例子:
/*** @Description: 泛型接口$* @Author: dyq* @Date: 2021年2月5日$*/
public interface GenericInterface<E> {public abstract void add(E e);public abstract E getE();
}
有两种实现方法先创建一个GenericInterfaceImpl01类
/*** @Description: $含有泛型的接口,第一种方式:定义接口的实现,实现接口,指定接口的泛型* @Author: dyq* @Date: $*/
public class GenericInterfaceImpl01 implements GenericInterface<String>{@Overridepublic void add(String s) {System.out.println(s);}@Overridepublic String getE() {return null;}
}
再创建一个GenericInterfaceImpl02实现类
/*** @Description: $含有泛型的接口第二种使用方式:接口使用什么泛型,* 实现类就使用什么泛型,类跟着接口走就相当于定义了一个含有泛型的类,* 创建对象的时候确定泛型的类型* @Author: dyq* @Date: $* public interface List<E>{* boolean add(E e);* E get(int index);* }* public class ArrayList<E> impLements List<E>{* }*/
public class GenericInterfaceImpl02<I> implements GenericInterface<I> {@Overridepublic void add(I i) {System.out.println(i);}@Overridepublic I getE() {return null;}
}
最后新建一个测试类
/*** @Description: $测试含有泛型的接口* @Author: dyq* @Date: $*/
public class GenericInterfaceTest {public static void main(String[] args) {//创建GenericInterfaceImplGenericInterfaceImpl01 gil =new GenericInterfaceImpl01();gil.add("哈哈哈~");//创建GenericInterfaceImpl01 定义输出的数据类型GenericInterfaceImpl02<Integer> gil2 = new GenericInterfaceImpl02<Integer>();gil2.add(10);GenericInterfaceImpl02<Double> gil3 = new GenericInterfaceImpl02<Double>();gil3.add(8.88);}
}
结果:
哈哈哈~
10
8.88
泛型的通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符< ?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。此时只能接受数据,不能往该集合中存储数据。
例子:
import java.util.ArrayList;
import java.util.Iterator;/*** @Description: $类型通配符一般是使用?代替具体的类型参数* @Author: dyq* @Date: $*/
public class GenericTest {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();list.add(1);list.add(2);ArrayList<String> list1 = new ArrayList<>();list1.add("a");list1.add("b");printArray(list);printArray(list1);}/*定义一个方法,能遍历所有类型的ArrayList集合* 这时候我们知道ArrList集合使用说明数据,可以泛型的通配符?来接收数据类型*/public static void printArray(ArrayList<?> list){//使用迭代器遍历集合Iterator<?> it =list.iterator();while (it.hasNext()){//it.next()方法。取出的元素是object,可以接收任意的数据类型Object o = it.next();System.out.println(o);}}
}结果:
1
2
a
b
类型通配符上限通过形如List来定义,如此定义就是通配符泛型值接受Number及其下层子类类型。
类型通配符下限通过形如 List<? super Number>来定义,表示类型只能接受Number及其三层父类类型,如 Object 类型的实例。
import java.util.ArrayList;
import java.util.Collection;/*** @Description: $泛型的上线限定:? extends E 代表我使用的泛型只能是E类型/本身* 泛型的下限定: ? super E 代表我使用的泛型只能是E类型/本身* @Author: dyq* @Date: $*/
public class GenericTest01 {public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>();Collection<String> list2 = new ArrayList<String>();Collection<Number> list3 = new ArrayList<Number>();Collection<Object> list4 = new ArrayList<Object>();getElement1(list1);getElement1(list2); //报错getElement1(list3);getElement1(list4);//报错getElement2(list1);//报错getElement2(list2);//报错getElement2(list3);getElement2(list4);/*类与类之间的继承关系Integer extends Number extents ObjectSTring extends Object* */}//泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类public static void getElement1(Collection<? extends Number> coll){}//泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类public static void getElement2(Collection<? super Number> collection){}
}结果:
自动装箱和自动拆箱
支持自动装箱的基本数据类型和其对应的包装类有:byte <-> Byte
short <-> Short
int <-> Integer
long <-> Long
float <-> Float
double <-> Double
char <-> Character
boolean <-> Boolean
这些基本数据类型都有其对应的包装类,因此可以进行自动装箱和拆箱
对于其他类型,如自定义的类或其他基本数据类型,是不支持自动装箱和拆箱的。如果需要将其转换,需要手动进行装箱和拆箱操作。
对于基本数据类型,由于其并不是一个类,所以无法构建其对象,通过操作对象的方法完成相关操作于是 Java提供对于基本数据类型的包装类int => Integer基本数据类型和包装类:Byte,Short,Integer,Long,Float,Double,Character,Booleanbyte,short,int,long,float,double,char,boolean对于包装类存在由 自动装箱和自动拆箱操作自动拆箱:对于包装类对象,可以直接将其赋予给对应的基本数据类型比如:Integer i2 = Integer.valueOf(str);int i3 = i2;int i4 = Integer.valueOf(str);自动装箱:当所需要的类型是包装类,但此时可以使用对应的基本数据类型来传递注意:对于包装类也可以使用 运算符 来完成相关操作
对于自定义的类或其他引用类型,Java 中并没有像基本数据类型和其对应的包装类那样提供自动装箱和自动拆箱的特性。自动装箱和拆箱仅适用于基本数据类型和其包装类之间的转换。如果需要在自定义类或其他引用类型之间进行转换,需要手动进行装箱和拆箱的操作。通常,这包括通过构造方法或者提供特定的方法来进行转换。举个例子,假设有一个自定义的类 MyClass:public class MyClass {private int value;public MyClass(int value) {this.value = value;}public int getValue() {return value;}
}
手动进行装箱和拆箱的示例:// 手动装箱
int intValue = 42;
MyClass myObject = new MyClass(intValue);// 手动拆箱
int retrievedValue = myObject.getValue();
在这个例子中,我们使用 new MyClass(intValue) 进行手动装箱,将基本数据类型的值封装到 MyClass 对象中。然后,通过调用 getValue() 方法进行手动拆箱,获取封装在对象中的值。
Collection list2 = new ArrayList();
Collection list3 = new ArrayList();
Collection list4 = new ArrayList();
getElement1(list1);getElement1(list2); //报错getElement1(list3);getElement1(list4);//报错getElement2(list1);//报错getElement2(list2);//报错getElement2(list3);getElement2(list4);/*类与类之间的继承关系Integer extends Number extents ObjectSTring extends Object* */
}
//泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){
}
//泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> collection){}
}
结果:
## 自动装箱和自动拆箱
支持自动装箱的基本数据类型和其对应的包装类有:
byte <-> Byte
short <-> Short
int <-> Integer
long <-> Long
float <-> Float
double <-> Double
char <-> Character
boolean <-> Boolean
这些基本数据类型都有其对应的包装类,因此可以进行自动装箱和拆箱
对于其他类型,如自定义的类或其他基本数据类型,是不支持自动装箱和拆箱的。如果需要将其转换,需要手动进行装箱和拆箱操作。
对于基本数据类型,由于其并不是一个类,所以无法构建其对象,通过操作对象的方法完成相关操作
于是 Java提供对于基本数据类型的包装类
int => Integer
基本数据类型和包装类:Byte,Short,Integer,Long,Float,Double,Character,Booleanbyte,short,int,long,float,double,char,boolean对于包装类存在由 自动装箱和自动拆箱操作自动拆箱:对于包装类对象,可以直接将其赋予给对应的基本数据类型比如:Integer i2 = Integer.valueOf(str);int i3 = i2;int i4 = Integer.valueOf(str);自动装箱:当所需要的类型是包装类,但此时可以使用对应的基本数据类型来传递注意:对于包装类也可以使用 运算符 来完成相关操作
对于自定义的类或其他引用类型,Java 中并没有像基本数据类型和其对应的包装类那样提供自动装箱和自动拆箱的特性。自动装箱和拆箱仅适用于基本数据类型和其包装类之间的转换。
如果需要在自定义类或其他引用类型之间进行转换,需要手动进行装箱和拆箱的操作。通常,这包括通过构造方法或者提供特定的方法来进行转换。
举个例子,假设有一个自定义的类 MyClass:
public class MyClass {
private int value;
public MyClass(int value) {this.value = value;
}public int getValue() {return value;
}
}
手动进行装箱和拆箱的示例:
// 手动装箱
int intValue = 42;
MyClass myObject = new MyClass(intValue);
// 手动拆箱
int retrievedValue = myObject.getValue();
在这个例子中,我们使用 new MyClass(intValue) 进行手动装箱,将基本数据类型的值封装到 MyClass 对象中。然后,通过调用 getValue() 方法进行手动拆箱,获取封装在对象中的值。
相关文章:
Java面对对象
Java面向对象 面对对象概述,类与对象,继承,重写与重载,多态,抽象,封装,包,泛型,异常 面对对象概述 什么是面向对象(OOP) 面向对象(Object Ori…...

代码随想录算法训练营|day24
第七章 回溯算法 77.组合代码随想录文章详解总结 77.组合 以n5,k3为例 (1)for循环遍历,递归选择符合要求的值加入path,len(path)k时,返回 statrtIndex保证每次递归取到的值不重复 剪枝:i<n-(k-len(path))1 后续需要k-len(pat…...
嵌入式学习日记 16
共用体 union 共用体名 { 成员列表; //各个变量 }; //表示定义一个共用体类型 注意: 1.共用体 初始化 --- 只能给一个值,默认是给到第一个成员变量的 2.共用体成员变量辅助 共用体用的数据最终存储的 --- 应该是最后一次给到的值。 但是只能…...

【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记(完结)
从高层设计的角度去探讨框架需要关注的问题。 参考:速读《Vue.js 设计与实现》 - 掘金 (juejin.cn) 系列目录: 标题博客第一篇:框架设计概览【Vue.js设计与实现】第一篇:框架设计概览-阅读笔记第二篇:响应系统【Vue.…...

数据结构—动态查找表
动态查找介绍 1. 动态查找的引入:当查找表以线性表的形式组织时,若对查找表进行插入、删除或排序操作,就必须移动大量的记录,当记录数很多时,这种移动的代价很大。 2. 动态查找表的设计思想:表结构本身是…...

Hbase-2.4.11_hadoop-3.1.3集群_大数据集群_SSH修改默认端口22为其他端口---记录025_大数据工作笔记0185
其实修改起来非常简单,但是在大数据集群中,使用到了很多的脚步,也需要修改, 这里把,大数据集群,整体如何修改SSH端口,为22022,进行总结一下: 0.hbase-2.4.11的话,hbase集群修改默认SSH端口22,修改成22022,需要修改 需要修改/opt/module/hbase-2.4.11/conf/hbase-env.sh 这里…...

c++学习第十四讲---STL常用容器---vector容器
vector容器: 1.vector基本概念: vector功能与数组类似,与数组不同的是,vector可以动态扩展。 2.vector构造函数: vector<T> v; //默认构造函数,创建数据类型T的容器 ve…...

数据结构-内部排序
简介 排序(Sorting):将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列 排序算法分为内部排序和外部排序 内部排序:在排序期间数据对象全部存放在内存的排序 外部排序&am…...

Qt加载网页崩溃 ASSERT:“m_adapterClient“ in file ...
1、软件启动后加载网页无异常,点击按钮,加载新网页时崩溃 崩溃代码: QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) { Q_UNUSED(type); return this; } 2、原因 Qt只是调用谷歌的浏览器引擎ÿ…...

合约短线高胜率策略-扭转乾坤指标使用说明
扭转乾坤指标使用说明 行情判断 双绿线 多趋势双红线 空趋势大绿线 小红线 多震荡大红线 小绿线 空震荡 进场条件 趋势行情进场 多趋势 多信号 底金叉 做多空趋势 空信号 顶死叉 做空 震荡行情进场 多震荡 多信号 底金叉 做多多震荡 空信号 顶死叉 做空空…...
DAY37:贪心算法738
今天写了一道题目,顺便看了一个很好的总结,这篇博客可以跳过。 Leetcode:738 单调递增的数字 因为最大的数字是9,当出现后面位数的数字比前面位数的数字小的时候,就把后面的数字都变成9,前面那个数字--。…...
计算机中的缓存与内存
在现代计算机系统中,缓存和内存扮演着至关重要的角色,它们共同协作以实现高性能和高效率的数据处理。本文将深入探讨缓存和内存的概念、功能以及它们在计算机系统中的作用。 缓存与内存:概念与功能 1. 内存(RAM)&…...

2.1总结
还是一样水更一天,就随便做了几个题,有一个周期有点长,后面更一篇长的 随手刷的一道水题,就不往今天的行程单添了 问题:最大公约数 题解:题目太水了,就是求三个数,其中两组的最大公…...

探索Pyecharts:绘制多彩日历图的艺术与技巧
Pyecharts绘制多种炫酷日历图参数说明代码实战 导言 在数据可视化领域,日历图是一种直观展示时间和数据关系的方式。Pyecharts是一个基于Echarts的Python库,可以方便地绘制各种图表,包括炫酷的日历图。本篇博客将介绍Pyecharts中绘制多种炫…...
响应标头Allow-Headers和Expose-Headers的区别和用法
Access-Control-Allow-Headers和Access-Control-Expose-Headers,简单的说,这两者都是前端和后端之间通过header传递数据的,主要的区别就是方向。 Access-Control-Allow-Headers 举个例子,如果我们前端向后端发起请求,…...
<网络安全>《13 上网行为管理》
1 概念 上网行为管理是指帮助互联网用户控制和管理对互联网的使用。其包括对网页访问过滤、上网隐私保护、网络应用控制、带宽流量管理、信息收发审计、用户行为分析等。 随着计算机、宽带技术的迅速发展,网络办公日益流行,互联网已经成为人们工作、生活…...

安全通道堵塞识别摄像机
当建筑物的安全通道发生堵塞时,可能会给人员疏散和救援带来重大隐患。为了及时识别和解决安全通道堵塞问题,专门设计了安全通道堵塞识别摄像机,它具有监测、识别和报警功能,可在第一时间发现通道堵塞情况。这种摄像机通常安装在通…...

2022 年全国职业院校技能大赛高职组云计算赛项试卷
【赛程名称】云计算赛项第二场-容器云 说明: 完成本任务需要两台安装了 CentOS7.9 操作系统的云主机: master 和 node。Chinaskill_Cloud_PaaS.iso 镜像包中有本次容器云部署所需的所有文件,运维所需的文件见附件。 某公司技术部产品开发上线…...
Android开发中,Vue 3处理回退按键事件
vue3有一些变化,按照网上有些文章的方法,发现行不通,通过一段时间的打印、尝试后,发现以下方法可行。 第一步)首先定义一个处理回退事件的js函数,一定是vue.methods中的函数,否则找不到this&am…...

three.js CSS3DRenderer、CSS3DSprite渲染HTML标签
有空的老铁关注一下我的抖音: 效果: <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red;position: relative;"></div><…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...