面向对象
Java语言是一种面向对象的程序设计语言,而面向对象思想(OOP)是一种程序设计思想,我们在面向对象思想的指引下,使用Java语言去设计、开发计算机程序。 这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。 它区别于面向过程思想(POP),强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去操作实现。
面向对象的三大特征
- 封装(Encapsulation)
- 继承(Inheritance)
- 多态(Polymorphism)
一、类和对象
类是一类具有相同特征的事物的抽象描述,是一组相同属性和方法的集合
//定义顾客类
public class Customer {
//成员变量,描述属性特征
String name;//姓名
int age;//年龄
int weight;//体重
//成员方法,描述行为特征
public void shopping(){
System.out.println("购物...");
}
}
//创建顾客对象
Customer c=new Customer();
//使用成员变量:对象名.属性 使用成员方法:对象名.方法名()
c.name="张三"; //访问对象的属性,赋值
c.age=18;
System.out.println(c.name+"--"+c.age); //访问对象的属性,获取值
c.shopping(); //访问对象的方法
二、 成员变量
变量的分类
根据定义位置不同分为:
-
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
-
成员变量:定义在类的成员位置,在方法体外,与方法(例如main方法)平行的位置。并且有修饰符修饰。(也叫属性)
class User{
//属性(或成员变量)
String name;
public int age;
boolean isMale;
//这个小括号里的叫局部变量
public void talk(String language){//language:形参,也是局部变量
System.out.println("我们使用" + language + "进行交流");
}
//这个方法里的food叫局部变量
public void eat(){
String food = "烙饼";//局部变量
System.out.println("北方人喜欢吃:" + food);
}
}
根据修饰的不同成员变量又分为:
- 类变量:或叫静态变量,有static修饰的成员变量。
- 实例变量:没有static修饰的成员变量。
成员变量的特点
- 成员变量有默认初始值
数据类型 | 默认值 |
---|---|
byte,short,int,long | 0 |
float,double | 0.0 |
char | 0或’\u0000’表现为空 |
boolean | false |
数组,类,接口等引用类型 | null |
- 类变量的值是所有对象共享的,而实例变量的值是每个对象独立的
//类变量(静态变量)
public static int a = 0;
//实例变量
private String nameString;
成员变量与局部变量的区别
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类的成员位置 | 声明在方法体中或其他局部区域内(方法声明上,构造方法,代码块等) |
修饰符 | public、private、static、final等 | 不能使用访问权限修饰符,可以使用final |
内存加载位置 | 堆或方法区(static修饰时) | 栈 |
初始化值 | 有默认初始化值 | 无默认初始化值 |
生命周期 | 同对象或类(static时) 的生命周期 | 随着方法的调用而存在,方法调用完毕即消失 |
三、方法
方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元。
把一个功能封装为方法的目的是,可以实现代码重用,从而简少代码量。
成员方法的分类
根据修饰不同方法主要分为两类:
- 实例方法:没有static修饰的方法,必须通过实例对象来调用。
- 静态方法:有static修饰的方法,也叫类方法,主要特点是可以由类名来调用。
【修饰符】 返回值类型 方法名(【参数列表:参数类型1 参数名1,参数类型2 参数名, ...... 】) {
方法体;
【return 返回值;】
}
- 修饰符:例如:public,static等都是修饰符
- 返回值类型: 表示方法运行的结果的数据类型,与"return"返回值搭配使用
- 无返回值:void
- 有返回值:可以是任意基本数据类型和引用数据类型
- 方法名:给方法起一个名字,要符合标识符的命名规则,尽量见名知意,能准确代表该方法功能的名字
- 参数列表:方法内部需要用到其他方法中的数据,需要通过参数传递的形式将数据传递过来,可以是基本数据类型、引用数据类型、也可以没有参数,什么都不写
- 方法体:特定功能的代码
- return:结束方法,可以返回方法的运行结果
- 可以返回不同类型的数据,对应匹配的返回值类型。
- 如果方法无返回值,可以省去return,并且返回值类型为void
可变参数
在JDK1.5之后,如果我们定义一个方法时,此时某个形参的类型可以确定,但是形参的个数不确定,那么我们可以使用可变参数。
格式:
【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){ }
(1)一个方法最多只能有一个可变参数
(2)如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个
public static void main(String[] args) {
int[] arr = {1, 4, 62, 431, 2};
int sum1 = getSum1(arr);
System.out.println(sum1);
int sum2 = getSum2(arr);
System.out.println(sum2);
int sum3 = getSum2(1, 4, 62, 431, 2);
System.out.println(sum3);
}
// 原始写法
public static int getSum1(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 可变参数写法
public static int getSum2(int... arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
方法的重载(overload)
方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
参数列表不同:指的是参数个数不同,数据类型不同,数据类型顺序不同。
//求两个整数的最大值
public int max(int a,int b){
return a>b?a:b;
}
//求三个整数的最大值
public int max(int a, int b, int c){
return max(max(a,b),c);
}//求两个小数的最大值
public double max(double a, double b){
return a>b?a:b;
}
//以上的方法就叫方法重载
递归
递归:指在当前方法内调用自己的这种现象。
递归的分类:
- 递归分为两种,直接递归和间接递归。
- 直接递归称为方法自身调用自己。
- 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:
- 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
- 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
//:计算1-100之间所有自然数的和
public static void main(String[] args) {
int sum = sum(100);
System.out.println("1-100的和:" + sum);
}
public static int sum(int n) {
if (n == 1) {
return 1;
} else {
return n + sum(n - 1);
}
}
}
四、封装
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
通俗的讲,封装就是把该隐藏的隐藏起来,该暴露的暴露出来。那么暴露的程度如何控制呢?就是依赖访问控制修饰符,也称为权限修饰符来控制。
权限修饰符
修饰符 | 本类 | 本包 | 其他包子类 | 任意位置 |
---|---|---|---|---|
private | √ | × | × | × |
缺省 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
封装实现:
public class Chinese {
private static String country;
private String name;
private int age;
private boolean marry;
}
//提供其get set方法
public static void setCountry(String c) {
country = c;
}
public static String getCountry() {
return country;
}
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
public void setMarry(boolean m) {
marry = m;
}
public boolean isMarry() {
return marry;
}
this关键字
如何解决局部变量与成员变量同名问题?
当局部变量与类变量(静态成员变量)同名时,在类变量前面加“类名.";
当局部变量与实例变量(非静态成员变量)同名时,在实例变量前面加“this.”
this代表当前对象的引用,即当前被创建的对象
public class Chinese {
private static String country;
private String name;
private int age;
public static void setCountry(String country) {
Chinese.country = country;
}
public static String getCountry() {
return country;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
构造器
我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。
可以,Java给我们提供了构造器。
Person p= new Person()
//这里的Person()就是构造器
构造器的作用
用于创建并初始化对象
public class test {
public static void main(String[] args) {
//创建类的对象:new + 构造器
Person p = new Person();//在无参构造器里输出一句话,那么在这里new时,就会执行无参构造器中的那句话
p.eat();
Person p1 = new Person("Tom");//构造器提供了有参构造,那么那么我就可以通过有参构造器直接赋值,也就是说当前我们创建的p1对象附上“Tom”,所以可以通过p1.name调用
System.out.println(p1.name);
}
}
class Person{
//属性
String name;
int age;
//构造器
public Person(){
System.out.println("Person().....");
}
public Person(String n){
name = n;
}
//
public Person(String n,int a){
name = n;
age = a;
}
//方法
public void eat(){
System.out.println("人吃饭");
}
public void study(){
System.out.println("人可以学习");
}
}
注意:构造器只为实例变量初始化,不为静态类变量初始化
构造器的语法格式
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
public class Student {
private String name;
private int age;
// 无参构造
public Student() {
}
// 有参构造
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
注意事项:
-
构造器名必须与它所在的类名必须相同。
-
它没有返回值,所以不需要返回值类型,甚至不需要void
-
如果你不提供构造器,系统会给出默认无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
-
如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
-
构造器是可以重载的,既可以定义参数,也可以不定义参数。
-
构造器的修饰符只能是权限修饰符,不能被其他任何修饰
-
一个类中可以定义多个构造器,这个构造器也称作重载
this调用构造器
构造器重载时,可以在一个构造方法中调用另一构造方法,以简化代码。
格式:this(参数列表)
/*
* this关键字的使用:
* 1.this可以用来修饰、调用:属性、方法、构造器
*
* 2.this修饰属性和方法:
* this理解为:当前对象 或 当前正在创建的对象
*
* 2.1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,
* 通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式
* 的使用"this.变量"的方式,表明此变量是属性,而非形参。
*
* 2.2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。
* 但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式
* 的使用"this.变量"的方式,表明此变量是属性,而非形参。
*
* 3. this调用构造器
* ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
* ② 构造器中不能通过"this(形参列表)"方式调用自己
* ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
* ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
* ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
*
*
*/
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(1);
System.out.println(p1.getAge());
p1.eat();
System.out.println();
Person p2 = new Person("Jerry",20);
System.out.println(p2.getAge());
}
}
class Person{
private String name;
private int age;
public Person(){
// this.eat();
String info = "Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)";
System.out.println(info);
}
public Person(String name){
this();
this.name = name;
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
//this.age = age;
//Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void eat(){
System.out.println("人吃饭");
this.study();
}
public void study(){
System.out.println("人学习");
}
}
标准JavaBean
JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求:
(1)类必须是具体的(非抽象)和公共的,
(2)并且具有无参数的构造方法,
(3)成员变量私有化,并提供用来操作成员变量的 set 和 get 方法。
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//其他成员方法列表
public String getInfo() {
return "姓名:" + name + ",年龄:" + age;
}
}
五、继承
继承的好处
- 提高代码的复用性。
- 提高代码的扩展性。
- 类与类之间产生了关系,是学习多态的前提。
继承的格式
通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
继承中成员变量的特点
子类可以继承父类的所有属性和方法,并直接使用(私有的例外)
父类的私有属性
- 子类继承父类的所有的属性,但是私有的属性不能直接访问(也可以理解为私有的属性不能被继承,官方文档中的说法)
- 子类不能直接进行访问父类的私有属性,但可以通过继承的getter/setter方法进行访问
父子类成员变量重名
父类的成员变量会被子类继承,并可以直接使用,也可以在子类中定义同名的成员变量,这样会隐藏掉父类的成员变量(不建议)。
(也可以理解为就近访问-就近原则)。如果还想在子类中访问父类的成员变量,可以使用关键字super。
super用于在当前类中访问其父类的成员,
super.父类成员变量名
super关键字
super的使用:调用属性和方法
- 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
super调用构造器
-
我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
-
"super(形参列表)"的使用,必须声明在子类构造器的首行!
-
我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
-
在构造器的首行,没有显式的声明"this(形参列表)“或"super(形参列表)”,则默认调用的是父类中空参的构造器:super()
-
在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
方法重写
当子类继承了父类的某个方法之后,发现这个方法并不能满足子类的实际需求,那么可以通过方法重写,覆盖父类的方法。
方法重写:子类中定义与父类中相同的方法,一般方法体不同,用于改造并覆盖父类的方法。
具体要求:
-
必须保证父子类之间方法的名称相同,参数列表也相同。
-
子类方法的返回值类型必须与父类方法的返回值类型相同或者为父类方法返回值类型的子类类型。
-
子类方法的访问权限必须不能小于父类方法的访问权限。(public > protected > 缺省 > private)
-
子类方法 抛出的异常不能大于父被重写的异常
class Phone {
public void sendMessage() {
System.out.println("发短信");
}
public void call() {
System.out.println("打电话");
}
public void showNum() {
System.out.println("来电显示号码");
}
}
//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum() {
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
//这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
注意事项:
- 静态方法不能被重写,方法重写指的是实例方法重写,静态方法属于类的方法不能被重写,而是隐藏。
- 私有等在子类中不可见的方法不能被重写
- final方法不能被重写
继承中的构造方法
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
-
构造方法的名字是与类名一致的。
所以子类是无法继承父类构造方法的。
-
构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量
所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。代码
如下:
class Fu {
private int n;
Fu() {
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi() {
// super(),调用父类构造方法
super();//默认隐藏
System.out.println("Zi()");
}
}
public class ExtendsDemo07 {
public static void main(String args[]) {
Zi zi = new Zi();
}
}
输出结果:
Fu()
Zi()
六、多态
例子:
public class Animal {
//String name = "animal";
public void eat(){
System.out.println("动物吃饭");
}
}
public class Dog extends Animal {//继承Animal类
//String name = "dog";
@Override
public void eat() {
System.out.println("狗啃骨头");
}
}
public class Person {
//喂狗吃饭
public void feed(Dog dog){
dog.eat();
}
}
public class Test1 {
public static void main(String[] args) {
Person p=new Person();
//测试人喂狗吃饭
p.feed(new Dog());
}
}
这时如果需要拓展功能,增加一个猫Cat类,让人喂Cat吃饭,应该如何做?
很简单,在Person类中增加喂猫的方法,那如果后期还要不断的增加新功能(新增各种动物类),就要不断增加Person类对应的方法。这里就出现了一个问题:违背了软件开发中的开闭原则(对修改关闭,对扩展开放),并且大量类同代码冗余,即每次新增的方法除了参数类型不同,其他都基本相同。
能不能用简单方式解决?
只需要在Person类中定义一个类似的方法,参数为Animal,其他全舍去,这就是多态的应用。
//修改后的Person类
public class Person {
//喂动物吃饭
public void feed(Animal animal){
animal.eat();
}
}
写法格式
父类类型 变量名 = 子类对象;
Ainmal a=new Dog();
理解:编译期a是个动物,运行时a是个狗;new 个Dog,a就是狗,new个Cat,a就是猫
例:
public class Test2 {
public static void main(String[] args) {
Animal a=new Animal();
a.eat();
Dog d=new Dog();
d.eat();
System.out.println("----------------"); //多态写法:父类的引用指向子类的对象。
Animal a1=new Dog();//狗也是一个动物
a1.eat();//运行时动态的调用了Dog的eat方法
Animal a2=new Cat();//猫也是一个动物
a2.eat();//运行时调用Cat的eat方法
}
}
多态的理解
引用类型变量在编译期呈现左边父类的行为特征,运行时呈现右边创建的不同子类对象的行为特征。
即:编译看左边,运行看右边。
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
//a1.catchMouse();//错误,遍看左边,catchMouse()是子类特有的方法,左边父类中没 有,编译失败
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}
多态的前提
-
有继承关系
-
有方法重写
如果没有方法重写,运行时调用子类的方法,实际还是调用的从父类继承的方法,不具有多态性。
多态的好处
- 提高程序的扩展性
- 降低类与类直接耦合度
多态的应用
父类类型作为方法形式参数,子类对象为实参。
public class Test01 {
public static void main(String[] args) {
showAnimalEat(new Dog());
//形参 Animal a,实参new Dog()
//实参给形参赋值 Animal a = new Dog() 多态 引用
showAnimalEat(new Cat());
//形参 Animal a,实参new Cat()
//实参给形参赋值 Animal a = new Cat() 多态 引用
}
/*
* 设计一个方法,可以查看所有动物的吃的行为
* 关注的是所有动物的共同特征:eat()
* 所以形参,设计为父类的类型
* 此时不关注子类特有的方法
*/
public static void showAnimalEat (Animal a){
a.eat();
// a.catchMouse();//错误,因为a现在编译时类型是Animal,只能看到父类中有的方法
}
}
数组元素类型声明为父类类型,实际存储的是子类对象
public class Test02 {
public static void main(String[] args) {
/*
* 声明一个数组,可以装各种动物的对象,看它们吃东西的样子
*/
Animal[] arr = new Animal[2]; //此时不是new Animal的对象,而是new Animal[]的数组对象
//在堆中开辟了长度为5的数组空间,用来装Animal 或它子类对象的地址
arr[0] = new Cat();//多态引用 左边arr[0] 是Animal类型,右边是new Cat()
//把Cat对象,赋值给Animal类型的变量
arr[1] = new Dog();
for (int i = 0; i < arr.length; i++) {
arr[i].eat(); //arr[i].catchMouse();错误,因为arr[i]现在编译时类型是Animal,只能看到父 类中有的方法
}
}
}
方法的返回值类型声明为父类的类型,实际返回值是子类对象
public class Test03 {
public static void main(String[] args) {
Animal c = buy("猫咪");
System.out.println(c.getClass());
c.eat();
}
/*
* 设计一个方法,可以购买各种动物的对象,此时不确定是那种具体的动物
*
* 返回值类型是父类的对象
*
* 多态体现在 返回值类型 Animal ,实际返回的对象是子类的new Cat(),或new Dog()
*/
public static Animal buy(String name){
if("猫咪".equals(name)){
return new Cat();
}else if("小狗".equals(name)){
return new Dog();
}
return null;
}
}
向上转型与向下转型
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换 的现象。但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多 态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。
向上转型:(其实也就是多态) 当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类 特有的变量和方法了 但是,运行时,仍然是对象本身的类型 此时,一定是安全的,而且也是自动完成的
向下转型:当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了 但是,运行时,仍然是对象本身的类型 此时,不一定是安全的,需要使用(类型)进行强制类型转换 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可 以通过isInstanceof关键字进行判断
package Casting;
public class Test{
public static void main(String[] args) {
//向上转型
Animal a=new Cat();
a.eat();
//向下转型,为了调用子类所特有的类
Cat c=(Cat)a;//这里调用的是cat类里的walk方法
//向下转型格式:子类名 自己定义个数=(子类类名)父类的自己起的名
c.walk();
Dog d=(Dog)a;
//这段代码可以通过编译,但是运行时,却报出了 ClassCastException
//这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有 任何继承关系,
//不符合类型转换的定义。
instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只 要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异 常。
instanceof关键字:判断变量类型是否匹配,结果为Boolean类型
变量名/对象 instanceof 数据类型
所以,转换前,我们最好先做一个判断
package Casting;
public class Test{
public static void main(String[] args) {
//向上转型
Animal a=new Cat();
a.eat();
if(a instanceof Cat){
Cat c=(Cat)a;
c.walk(); //调用的是Cat的Walk
}else if(a instanceof Dog){
Dog d=(Dog)a;
d.walk(); //调用的是Dog的Walk
}
那么,哪些instanceof判断会返回true呢?
- 对象/变量的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
- 对象/变量的运行时类型<= instanceof后面数据类型,才为true
native关键字
native:本地的,原生的 用法:
只能修饰方法
表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。 但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。 JVM内存的管理
final关键字
final:最终的,不可更改的,它的用法有:
1.修饰类
表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
2. 修饰方法
表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
3.声明常量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使 用大写字母。
- 如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初 始化块赋值、实例变量还可以在构造器中赋值)
public class Test{
public static void main(String[] args){
final int MIN_SCORE = 0;
final int MAX_SCORE = 100;
}
}
class Chinese{
public static final String COUNTRY = "中华人民共和国";
private String name;
public Chinese( String name){
super();
this.name = name;
}
public Chinese() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//final修饰的没有set方法
public static String getCountry() {
return COUNTRY;
}
}
七、Object根父类
如何理解根父类
类 java.lang.Object 是类层次结构的根类,即所有类的父类。每个类都直接或间接继承Object类。
Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用 所有对象(包括数组)都实现这个类的方法。 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
public class MyClass /*extends Object*/ {
// ...
}
Object类的API
API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的 字典 ,是 JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得 知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么 我们需要查看src源码。 根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5 个:
(1)toString()
public String toString()
①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"
②像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息
③当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
- 因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存 地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的 toString()。 例如自定义的Person类:
- 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
// 省略构造器与Getter Setter
}
(2)getClass()
public final Class<?> getClass():获取对象的运行时类型
因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因 此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new String();
System.out.println(obj.getClass());//运行时类型
}
(3)finalize()
protected void finalize():用于最终清理内存的方法
public class TestFinalize {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyData my = new MyData();
}
System.gc();//通知垃圾回收器来回收垃圾
try {
Thread.sleep(2000);//等待2秒再结束main,为了看效果
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyData{
@Override
protected void finalize() throws Throwable {
System.out.println("轻轻的我走了...");
}
}
面试题:对finalize()的理解?
- 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调 用。
- 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回 收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活” 的代码
- 每一个对象的finalize方法只会被调用一次。
- 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码 申请的资源内存
(4)hashCode()
public int hashCode():返回每个对象的hash值。
hashCode 的常规协定:
- ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
- ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。
主要用于后面当对象存储到哈希表等容器中时,为了提高存储和查询性能用的。
(5)equals()
- 是一个方法,而非运算符
- 只能适用于引用数据类型
public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”
①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
②我们可以选择重写,重写有些要求:
- 如果重写equals,那么一定要一起重写hashCode()方法,因为规定:
-
如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;
-
如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;
-
如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是 false
-
- 如果重写equals,那么一定要遵循如下几个原则:
- 自反性:x.equals(x)返回true
- 传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true
- 一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致
- 对称性:x.equals(y)与y.equals(x)结果应该一样 非空对象与null的equals一定是false
八、抽象类
abstract
语法格式
抽象方法 : 没有方法体的方法。
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
例:
public abstract viod eat();
//注意:抽象方法没有方法体
//包含抽象方法的类,一定是一个抽象类,反之,抽象类中可以没有抽象方法
//若子类重写了父类中的所有抽象方法后,此子类才可以实例化
//如果子类没有重写父类中所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
抽象类:被abstract所修饰的类。
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
//当我们用abstract修饰类,类不可实例化
//抽象类中一定有构造器,便于子类实例化调用(涉及,子类对象实例化全过程)
//开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
练习:
1、声明抽象父类:Person,包含抽象方法: public abstract void walk(); public abstract void eat();
2、声明子类Man,继承Person 重写walk():大步流星走路 重写eat():狼吞虎咽吃饭 新增方法: public void smoke()实现为吞云吐雾
3、声明子类Woman,继承Person 重写walk():婀娜多姿走路 重写eat():细嚼慢咽吃饭 新增方 法:public void buy()实现为买买买…
4、在测试类中创建子类对象,调用方法测试
public abstract class Person{
public abstract void walk();
public abstract void eat();
}
public class Man extends Person{
@Override
public void walk() {
System.out.println("大步流星走路");
}
@Override
public void eat() {
System.out.println("狼吞虎咽吃饭");
}
public void smoke(){
System.out.println("吞云吐雾");
}
}
public class Woman extends Person{
@Override
public void walk() {
System.out.println("婀娜多姿走路");
}
@Override
public void eat() {
System.out.println("细嚼慢咽吃饭");
}
public void bay(){
System.out.println("买买买");
}
}
public class Demo2 {
public static void main(String[] args) {
Man m = new Man();
m.eat();
m.smoke();
m.walk();
Woman w = new Woman();
w.bay();
w.eat();
w.walk();
}
}
抽象类特点
- 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
- 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意 义。
-
抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
- 理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造 方法。
-
抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊 的类结构设计。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也 是抽象类。
- 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的 方法,没有意义。
九、接口(interface)
定义格式与特点
接口是静态常量与抽象方法定义的集合。跟定义类的方式类似,接口使用关键字interface定义。
1.接口的声明格式
【修饰符】 interface 接口名{
}
//使用interface来定义
//Java中,接口和类是并列的两个结构
如何定义接口,定义接口中的成员
public class Demo3 {
public static void main(String[] args) {
System.out.println(Flyable.Max_SPEED);
System.out.println(Flyable.MIN_SPEED);
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable {
//JDK7及以前,接口只能定义全局常量和抽象方法
//全局常量:public static final修饰,但是书写时可以省略,默认存在
public static final int Max_SPEED = 7990;
int MIN_SPEED = 1;
//抽象方法
public abstract void fly();
//省略了public abstract
void stop();
/* public Flyable(){
//接口中不能定义构造器,意味着接口不可以实例化
}*/
}
//java开发中,接口通过让类去实现(implements)的方式来使用
//如果实现类重写了接口中所有的抽象方法,则此实现类就可以实例化
//如果实现类没有重写接口中所有的抽象方法,则此实现类仍为一个抽象类
class Plane implements Flyable{
//抽象类的方法重写习惯叫覆盖,实现
@Override
public void fly() {
System.out.println("通过飞机引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员停止");
}
}
2.接口的特点
接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里 通常是定义一组公共方法。
-
接口没有构造方法,不能创建对象。
-
成员变量默认自带修饰符public static final,即为静态常量。
-
抽象方法默认自带修饰符public abstract(jdk8之前版本接口中方法只能是抽象方法)
-
接口是用来被实现的,其实现类必须重写它的所有抽象方法,除非实现类是个抽象类
-
接口可以多实现,一类可以同时实现多个接口
弥补了Java单继承的局限性
-
接口可以继承接口,接口之间支持多继承
-
在JDK1.8时,接口中允许声明默认方法和静态方法:
- 公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
- 公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
-
在JDK1.9时,接口又增加了私有方法
3.面试题
1.为什么接口中只能声明公共的静态的常量?
因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范 时,不能去随意修改和触碰这些底线,否则就有“危险”。
例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
2.为什么JDK1.8之后要允许接口定义静态方法和默认方法呢?这样做违反了接口作为一个抽象 标准定义的概念。
关于静态方法:
因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这 样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的, 那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。接口中的静态 方法不能被实现类覆盖,并且只能由接口名调用。
关于默认方法:
(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,原来使 用这些接口的类就会有问题,需要修改实现新增的抽象方法,那么为了保持与旧版本代码的 兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、 Comparator等接口提供了丰富的默认方法。
(2)当我们接口的某个抽象方法,在很多实现
void read(); void write(); //默认方法 public default void start(){ System.out.println(“开始”); } public default void stop(){ System.out.println(“结束”); } //静态方法 public static void show(){ System.out.println(“USB 3.0可以同步全速地进行读写操作”); } }
类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可 以选择重写,也可以选择不重写。
3.我们说接口是规范,规范时需要公开让大家遵守的,为什么JDK1.9要允许接口定义私有方法 呢?
私有方法:因为有了默认方法和静态方法这样具有具体实现的方法,那么就可能出现多个方 法由共同的代码可以抽取,而这些共同的代码抽取出来的方法又只希望在接口内部使用,所 以就增加了私有方法
实现接口
接口是一套规范,是功能的拓展,使用接口,就需要类实现接口,理解为符合接口规范的类或额外拓展 功能的类。
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实 现的动作类似继承,格式相仿,只是关键字不同,实现使用 implements 关键字。
1.实现接口语法格式
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
注意:
-
如果接口的实现类是非抽象类,那么必须重写接口中所有抽象方法。
-
默认方法可以选择保留,也可以重写。
- 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有 默认方法的概念了
-
不能重写静态方法
2.如何调用对应的方法
- 对于接口的静态方法,直接使用“接口名.”进行调用即可
- 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
- 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
- 接口不能直接创建对象,只能创建实现类的对象
class MobileHDD implements Usb3{
//重写/实现接口的抽象方法,【必选】
public void read() {
System.out.println("读数据");
}
public void write(){
System.out.println("写数据");
}
//重写接口的默认方法,【可选】
//重写默认方法时,default单词去掉
public void end(){
System.out.println("清理硬盘中的隐藏回收站中的东西,再结束");
}
}
练习
1、声明一个LiveAble接口
-
包含两个抽象方法:
-
void eat();
-
void breathe();
-
-
包含默认方法 default void sleep(),实现为打印“静止不动”
-
包含静态方法 static void drink(),实现为“喝水”
2、声明动物Animal类,实现LiveAble接口。
- void eat();实现为“吃东西”,
- void breathe();实现为"吸入氧气呼出二氧化碳"
- void sleep()重写为”闭上眼睛睡觉"
3、声明植物Plant类,实现LiveAble接口。
- void eat();实现为“吸收营养”
- void breathe();实现为"吸入二氧化碳呼出氧气"
4、在测试类中,分别创建两个实现类的对象,调用对应的方法。通过接口名,调用静态方法 定义接口:
public interface Liveable{
void eat();
void breathe();
public default void sleep(){
System.out.println("静止不动");
}
public static void drink(){
System.out.println("喝水");
}
}
class Animal implements Liveable{
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breathe() {
System.out.println("吸入氧气呼出二氧化碳");
}
@Override
public void sleep() {
System.out.println("闭上眼睛睡觉");
}
}
class Plant implements Liveable{
@Override
public void eat() {
System.out.println("吸收营养");
}
@Override
public void breathe() {
System.out.println("吸入二氧化碳呼出氧气");
}
}
public class Demo4 {
public static void main(String[] args) {
Animal a = new Animal();
a.breathe();
a.eat();
a.sleep();
Plant p = new Plant();
p.breathe();
p.eat();
Liveable.drink();
}
}
接口的多实现
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口 的,这叫做接口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
接口中,有多个抽象方法时,实现类必须重写所有抽象方法。如果抽象方法有重名的,只需要重 写一次。
例:
//定义多个接口
interface A{
public abstract void showA();
public abstract void show();
}
interface B{
public abstract void showB();
public abstract void show();
}
//定义实现类:
public calss C implements A,B{
@Override
public void showA() {
System.out.println("showA");
}
@Override
public void showB() {
System.out.println("showB");
}
@Override
public void show() {
System.out.println("show");
}
}
默认方法冲突问题(了解)
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名, 子类就近选择执行父类的成员方法。
当一个类同时实现了多个接口,而多个接口中包含方法签名相同的默认方法时,无论你多难抉择,最终都是要做出选择的。
interface A{
public default void d(){
System.out.println("今晚7点-8点陪我吃饭看电影");
}
}
interface B{
public default void d(){
System.out.println("今晚7点-8点陪我逛街吃饭");
}
}
//选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。
class C implements A,B{
@Override
public void d() {
A.super.d();
}
}
//选择自己完全重写:
class D implements A,B{
@Override
public void d() {
System.out.println("自己待着");
}
}
接口的多继承
一个接口能继承另一个或者多个接口,接口的继承也使用 extends 关键字,子接口继承父接口的方 法。
//定义父接口
interface A {
void a();
public default void methodA(){
System.out.println("AAAAAAAAAAAAAAAAAAA"); }
}
interface B {
void b();
public default void methodB(){
System.out.println("BBBBBBBBBBBBBBBBBBB");
}
}
//定义子接口
interface C extends A,B{
@Override
public default void methodB() {
System.out.println("CCCCCCCCCCCCCCCCCCCC");
}
}
//子接口重写默认方法时,default关键字可以保留。
//子类重写默认方法时,default关键字不可以保留。
class D implements C{
@Override
public void a() {
System.out.println("xxxxx");
}
@Override
public void b() {
System.out.println("yyyyy");
}
}
class E implements A,B,C{//效果和上面的D是等价的
@Override
public void b() {
System.out.println("xxxxx");
}
@Override
public void a() {
System.out.println("yyyyy");
}
}
接口与实现类对象的多态引用
实现类实现接口,类似于子类继承父类,因此,接口类型的变量与实现类的对象之间,也可以构成多态 引用。通过接口类型的变量调用方法,最终执行的是你new的实现类对象实现的方法体。
public class TestInterface {
public static void main(String[] args) {
Flyable b = new Bird();
b.fly();
Flyable k = new Kite();
k.fly();
}
}
interface Flyable{
//抽象方法
public void fly() {
System.out.println("展翅高飞");
}
}
class Kite implements Flyable{
@Override
public void fly() {
System.out.println("别拽我,我要飞");
}
}
经典接口介绍与使用
1、java.lang.Comparable
我们知道基本数据类型的数据(除boolean类型外)需要比较大小的话,之间使用比较运算符即可,但 是引用数据类型是不能直接使用比较运算符来比较大小的。因为引用类型本身之间本身没有大小可言 (包装类除外),需要我们根据需求来决定那一个对象是大还是小,那么不同的引用类型就可能判断依 据不一样。我们一定义一个接口,给出比较两个对象大小的方法,但具体实现由不同的实现类完成。
Java给所有引用数据类型的大小比较,指定了一个标准接口,就是java.lang.Comparable接口:
package java.lang;
public interface Comparable{
int compareTo(Object obj);
}
那么我们想要使得我们某个类的对象可以比较大小,怎么做呢?步骤:
第一步:哪个类的对象要比较大小,哪个类就实现java.lang.Comparable接口,并重写方法
- 方法体就是你要如何比较当前对象和指定的另一个对象的大小
第二步:对象比较大小时,通过对象调用compareTo方法,根据方法的返回值决定谁大谁小。
- this对象(调用compareTo方法的对象)大于指定对象(传入compareTo()的参数对象)返回正整 数
- this对象(调用compareTo方法的对象)小于指定对象(传入compareTo()的参数对象)返回负整 数
- this对象(调用compareTo方法的对象)等于指定对象(传入compareTo()的参数对象)返回零
//例:
public class Demo5 {
public static void main(String[] args) {
Student s1 = new Student(1, "张三", 89);
Student s2 = new Student(2, "李四", 89);
if (s1.compareTo(s2) > 0) {
System.out.println("s1>s2");
} else if (s1.compareTo(s2) < 0) {
System.out.println("s1<s2");
} else {
System.out.println("s1 = s2");
}
}
}
class Student implements Comparable {
private int id;
private String name;
private int score;
public Student() {
}
public Student(int id, String name, int score) {
this.id = id;
this.name = name;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(Object o) {
//这些需要强制,将o对象向下转型为Student类型的变量,才能调用Student类中的属性
Student stu = (Student) o;
if (this.score != stu.score) {
return this.score - score;
} else {//成绩相同,按照学号比较大小
return this.id - stu.id;
}
}
}
练习:
- 声明一个Employee员工类,包含编号、姓名、薪资,实现Comparable接口,要求,按照薪资比 较大小,如果薪资相同,按照编号比较大小。
- 声明一个测试类TestEmployee类,在main中创建Employee[]数组,长度为5,并且存储5个员工 对象,现在要求用冒泡排序,实现对这个数组进行排序,遍历结果。
public class Demo6 {
public static void main(String[] args) {
Employee[] arr = new Employee[5];
arr[0] = new Employee(1, "张三", 13000);
arr[1] = new Employee(5, "李四", 13000);
arr[2] = new Employee(3, "王五", 14000);
arr[3] = new Employee(4, "赵六", 7000);
arr[4] = new Employee(2, "钱七", 9000);
System.out.println("排序前员工列表:");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 1
for (int i = 0; i < arr.length-1; i++) {
//lengt永远比索引大于1
for (int j = 0; j < arr.length - i-1; j++)
if (arr[j].compareTo(arr[j + 1]) > 0) {
//1 交换
// 0 不动
Employee temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("排序后的员工列表");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
class Employee implements Comparable {
private int id;
private String name;
private double salary;
public Employee() {
}
public Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
'}';
}
@Override
public int compareTo(Object o) {
Employee e = (Employee) o;
if (this.getSalary() != e.getSalary()) {
return Double.compare(this.getSalary(), e.getSalary());
}
return this.id - e.id;
}
}
2、java.util.Comparator
思考:
(1)如果一个类,没有实现Comparable接口,而这个类你又不方便修改(例如:一些第三方的类,你 只有.class文件,没有源文件),那么这样类的对象也要比较大小怎么办?
(2)如果一个类,实现了Comparable接口,也指定了两个对象的比较大小的规则,但是此时此刻我不 想按照它预定义的方法比较大小,但是我又不能随意修改,因为会影响其他地方的使用,怎么办?
JDK在设计类库之初,也考虑到这种情况了,所以又增加了一个java.util.Comparator接口。
package java.util;
public interface Comparator{
int compare(Object o1,Object o2);
}
那么我们想要比较某个类的两个对象的大小,怎么做呢?步骤:
第一步:编写一个类,我们称之为比较器类型,实现java.util.Comparator接口,并重写方法
- 方法体就是你要如何指定的两个对象的大小
第二步:比较大小时,通过比较器类型的对象调用compare()方法,将要比较大小的两个对象作为 compare方法的实参传入,根据方法的返回值决定谁大谁小。
-
o1对象大于o2返回正整数
-
o1对象小于o2返回负整数
-
o1对象等于o2返回零
import java.util.Comparator;
public class Demo7 {
public static void main(String[] args) {
Student1 stu1 = new Student1("张三", 89);
Student1 stu2 = new Student1("李四", 78);
StudentScoreCompare ssc = new StudentScoreCompare();
if (ssc.compare(stu1, stu2) > 0) {
System.out.println(stu1 + ">" + stu2);
} else if (ssc.compare(stu1, stu2) < 0) {
System.out.println(stu1 + "<" + stu2);
} else {
System.out.println(stu1 + "=" + stu2);
}
}
}
class Student1 {
private String name;
private int score;
public Student1(String name, int score) {
super();
this.name = name;
this.score = score;
}
public Student1() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", score=" + score + "]";
}
}
class StudentScoreCompare implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Student1 s1 = (Student1) o1;
Student1 s2 = (Student1) o2;
return s1.getScore() - s2.getScore();
}
}
3、java.lang.Cloneable标记接口
标记接口:没有任何方法的接口。类似注解。
在java.lang.Object类中有一个方法:
protected Object clone()throws CloneNotSupportedException
所有类型都可以重写这个方法,它是获取一个对象的克隆体对象用的,就是造一个和当前对象各种属性 值一模一样的对象。当然地址肯定不同。
我们在重写这个方法后时,调用super.clone(),发现报异常CloneNotSupportedException,因为我们 没有实现java.lang.Cloneable接口。
class Teacher implements Cloneable {
private int id;
private String name;
public Teacher(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Teacher() {
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [id=" + id + ", name=" + name + "]";
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Teacher other = (Teacher) obj;
if (id != other.id) return false;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
return true;
}
}
public class TestClonable {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher src = new Teacher(1, "柴老师");
Object clone = src.clone();
System.out.println(clone);
System.out.println(src == clone);
System.out.println(src.equals(clone));
}
}
十、内部类
内部类都有哪些形式?
根据内部类声明的位置(如同变量的分类),我们可以分为:
(1)成员内部类:
- 静态成员内部类
- 非静态成员内部类
(2)局部内部类
- 有名字的局部内部类
- 匿名的内部类
静态内部类
语法格式:
【修饰符】 class 外部类{
【其他修饰符】 static class 内部类{
}
}
静态内部类的特点:
-
和其他类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父 接口无关
- 可以在静态内部类中声明属性、方法、构造器等结构,包括静态成员
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
-
和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
-
只可以在静态内部类中使用外部类的静态成员
- 在静态内部类中不能使用外部类的非静态成员哦
-
在外部类的外面不需要通过外部类的对象就可以创建静态内部类的对象
-
如果在内部类中有变量与外部类的静态成员变量同名,可以使用“外部类名."进行区别
public class TestInner{
public static void main(String[] args){
Outer.Inner in= new Outer.Inner();
in.inMethod();
Outer.Inner.inTest();
Outer.Inner.inFun(3);
}
}
class Outer{
private static int a = 1;
private int b = 2;
protected static class Inner{
static int d = 4;//可以
void inMethod(){
System.out.println("out.a = " + a);
//System.out.println("out.b = " + b);//错误的
}
static void inTest(){
System.out.println("out.a = " + a); }
static void inFun(int a){
System.out.println("out.a = " + Outer.a);
System.out.println("local.a = " + a); }
}
}
非静态成员内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 class 内部类{
}
}
非静态内部类的特点:
-
和其他类一样,它只是定义在外部类中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父 接口无关
- 可以在非静态内部类中声明属性、方法、构造器等结构,但是不允许声明静态成员,但 是可以继承父类的静态成员,而且可以声明静态常量。
- 可以使用abstract修饰,因此它也可以被其他类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名和$符号。
-
和外部类不同的是,它可以允许四种权限修饰符:public,protected,缺省,private
- 外部类只允许public或缺省的
-
还可以在非静态内部类中使用外部类的所有成员,哪怕是私有的
-
在外部类的静态成员中不可以使用非静态内部类哦
- 就如同静态方法中不能访问本类的非静态成员变量和非静态方法一样
-
在外部类的外面必须通过外部类的对象才能创建非静态内部类的对象
- 因此在非静态内部类的方法中有两个this对象,一个是外部类的this对象,一个是内部 类的this对象
public class TestInner{
public static void main(String[] args){
Outer out = new Outer();
Outer.Inner in= out.new Inner();
in.inMethod();
Outer.Inner inner = out.getInner();
inner.inMethod();
}
}
class Father{
protected static int c = 3;
}
class Outer{
private static int a = 1;
private int b = 2;
protected class Inner extends Father{
// static int d = 4;//错误
int b = 5;
void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + Outer.this.b);
System.out.println("in.b = " + b);
System.out.println("father.c = " + c);
}
}
public static void outMethod(){
// Inner in = new Inner();//错误的
}
public Inner getInner(){
return new Inner();
}
}
局部内部类
语法格式:
【修饰符】 class 外部类{
【修饰符】 返回值类型 方法名(【形参列表】){
【final/abstract】 class 内部类{
}
}
}
局部内部类的特点:
- 和外部类一样,它只是定义在外部类的某个方法中的另一个完整的类结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父 接口无关
- 可以在局部内部类中声明属性、方法、构造器等结构,但不包括静态成员,除非是从父 类继承的或静态常量
- 可以使用abstract修饰,因此它也可以被同一个方法的在它后面的其他内部类继承
- 可以使用final修饰,表示不能被继承
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编 号。
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中是否能访问外部类的静态还是非静态的成员,取决于所在的方法
- 局部内部类中还可以使用所在方法的局部常量,即用final声明的局部变量 JDK1.8之后,如果某个局部变量在局部内部类中被使用了,自动加final
class Outer{
private static int a = 1;
private int b = 2;
public static void outMethod(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
// System.out.println("out.b = " + b);//错误的,因为outMethod是静 态的
System.out.println("out.local.c = " + c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
final int c = 3;
class Inner{
public void inMethod(){
System.out.println("out.a = " + a);
System.out.println("out.b = " + b);//可以,因为outTest是飞静态 的
System.out.println("method.c = " + c);
}
}
Inner in = new Inner(); in.inMethod();
}
}
匿名内部类
语法格式
new 父类(【实参列表】){
重写方法...
}
//()中是否需要【实参列表】,看你想要让这个匿名内部类调用父类的哪个构造器,如果调用父类的无 参构造,那么()中就不用写参数,如果调用父类的有参构造,那么()中需要传入实参
new 父接口(){
重写方法...
}
//()中没有参数,因为此时匿名内部类的父类是Object类,它只有一个无参构造
匿名内部类是没有名字的类,因此在声明类的同时就创建好了唯一的对象。
**注意: 匿名内部类是一种特殊的局部内部类,只不过没有名称而已。所有局部内部类的限制都适用于匿名 内部类。**例如:
- 在匿名内部类中是否可以使用外部类的非静态成员变量,看所在方法是否静态
- 在匿名内部类中如果需要访问当前方法的局部变量,该局部变量需要加final
思考:这个对象能做什么呢?
(1)调用某个方法
(2)赋值给父类/父接口的变量,通过多态引用使用这个对象
(3)作为某个方 法调用的实参
使用方式一:匿名内部类的对象直接调用方法
public class Demo8 {
public static void main(String[] args) {
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
interface A{
void a();
}
使用方式二:通过父类或父接口的变量多态引用匿名内部类的对象
public class Demo9 {
public static void main(String[] args) {
B obj=new B() {
@Override
public void b() {
System.out.println("bbbb");
}
};
obj.b();
}
}
interface B{
void b();
}
使用方式三:匿名内部类的对象作为实参
interface A{
、void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa"); }
});
}
}
十一、static关键字
static是一个修饰符,可以修饰:
- 成员变量,我们称为类变量,或静态变量,表示某个类的所有对象共享的数据
- 成员方法,我们称为类方法,或静态方法,表示不需要实例对象就可以调用的方法,使用“类 名."进行调用
- 父类的静态方法可以被继承不能被重写
- 父接口的静态方法不能被实现类继承
- 代码块,我们称为静态代码块,或静态初始化块,用于为静态变量初始化,每一个类的静态代码块 只会执行一次,在类第一次初始化时执行
- 成员内部类,我们称为静态成员内部类,简称静态内部类,不需要外部类实例对象就可以使用的内 部类,在静态内部类中只能使用外部类的静态成员
- static不能修饰top-level的类
- 静态导入
import static 包.类名.静态成员;
import static 包.类名.*;
不能和abstract一起使用的修饰符?
(1)abstract和final不能一起修饰方法和类
(2)abstract和static不能一起修饰方法
(3)abstract和native不能一起修饰方法
(4)abstract和private不能一起修饰方法
static和final一起使用:
(1)修饰方法:可以,因为都不能被重写
(2)修饰成员变量:可以,表示静态常量
(3)修饰局部变量:不可以,static不能修饰局部变量
(4)修饰代码块:不可以,final不能修改代码块
(5)修饰内部类:可以一起修饰成员内部类,不能一起修饰局部内部类
十二、单例模式
/*
单例设计模式:
1.所谓类的单例设计模式,就是采用一定的方法保证在整个的软件系统中,对某个类只能存在一个对象的实例
2.如何实现?
饿汉式VS懒汉式
3.区分饿汉式和懒汉式
饿汉式:坏处:对象加载时间过长
好处:线程安全
懒汉式:好处:延迟对象的创建
坏处:线程相较不安全
*/
public class Demo10 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1==bank2);//true
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1==order2);//true
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//要求此对象也必须声明为静态的
private static Bank instance= new Bank();
//3.提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
//懒汉式
class Order {
//1.私有化类的构造器
private Order() {
}
//2.声明当前类的对象
//此对象也必须声明为static的
private static Order instance = null;
//3.声明一个public,static的返回当前类对象的方法
public static Order getInstance() {
if (instance == null) {
instance = new Order();
}
return instance;
}
}
注解
注解是以“@注释名”在代码中存在的,还可以添加一些参数值
系统预定义的三个最基本的注解
1、@Override
用于检测被修饰的方法为有效的重写方法,如果不是,则报编译错误!
只能标记在方法上。
它会被编译器程序读取。
2、@Deprecated
用于表示被标记的数据已经过时,不建议使用。
可以用于修饰 属性、方法、构造、类、包、局部变量、参数。
它会被编译器程序读取。
3、@SuppressWarnings
抑制编译警告。
可以用于修饰类、属性、方法、构造、局部变量、参数
它会被编译器程序读取。
Java中文档注释
- @author 标明开发该类模块的作者,多个作者之间使用,分割
- @version 标明该类模块的版本 @see 参考转向,也就是相关主题
- @since 从哪个版本开始增加的
- @param 对方法中某参数的说明,如果没有参数就不能写
- @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
- @throws/@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常 就不能写
- 其中 @param @return 和 @exception 这三个标记都是只用于方法的。
- @param的格式要求:@param 形参名 形参类型 形参说明
- @return 的格式要求:@return 返回值类型 返回值说明
- @exception 的格式要求:@exception 异常类型 异常说明
- @param和@exception可以并列多个
javadoc.exe就是这些注解的信息处理流程。