多态的概述、优势
什么是多态?
多种形态
- 同类型的对象,执行同一个行为,会表现出不同的行为特征。
- 相同的行为,不同的实现。
例子: 动物都有吃的行为, 兔子吃胡萝卜, 猫吃鱼, 狗吃肉 .
相同的行为( 吃东西) 不同的实现( 胡萝卜,鱼,肉 )
多态的常见形式
父类类型 对象名称 = new 子类构造器;
接口 对象名称 = new 实现类构造器;
注意: 前提是两个类之间必须有 继承关系 和 实现关系
代码演示
测试类:
public class Test { public static void main(String[] args) { //1、多态的形式:父类类型 对象名称 = new 子类构造器; // 接口 对象名称 = new 实现类构造器; Animal a = new Dog(); go(a); a.run();//方法调用:编译看左边,运行看右边 System.out.println(a.name);//父类动物//变量调用,编译看左边,运行也看左边 Animal a2 = new Tortoise(); go(a2); a2.run(); System.out.println(a2.name);//父类动物 } /** 要求:所有的动物都可以进来比赛 */ public static void go(Animal a){ System.out.println("开始。。。"); a.run(); System.out.println("结束。。。"); } }
父类:
public abstract class Animal { public String name = "父类动物"; public abstract void run(); }
子类:
public class Dog extends Animal{ public String name = "子类狗"; @Override public void run() { System.out.println("跑的贼快---"); } /** 独有功能 */ public void lookDoor(){ System.out.println("在看!!!"); } } public class Tortoise extends Animal{ public String name = "子类乌龟"; @Override public void run() { System.out.println("根本跑不了---"); } /** 独有功能 */ public void layEggs(){ System.out.println("在下蛋---"); } }
多态中成员访问特点:
1、方法调用:编译看左边,运行看右边
2、变量调用:编译看左边,运行也看左边。(多态侧重行为多态)
多态的前提:
有继承/实现关系;有父类引用指向子类对象;有方法重写。
多态的优势?
1、在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
2、定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
多态下会产生一个问题:多态下不能使用子类的独有功能。代码演示(测试类):父类与子类同上面的。
public class Test { public static void main(String[] args) { Animal t = new Tortoise(); go(t); Animal d = new Dog(); go(d); //a2.lookDoor();//多态下不能访问子类独有功能 } /** * 要求:所有的动物都可以进来比赛 */ public static void go(Animal a) { System.out.println("开始。。。"); a.run(); System.out.println("结束。。。"); } }
多态的类型转换问题
1、自动类型转换(从子到父):又叫做向上转型
子类对象赋值给父类类型的变量指向。
2、强制类型转换(从父到子):又叫做向下转型
- 此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
- 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
- 注意:如果转型后的类型和对象真实类型不是同一类型,那么在转换时就会出现数据类型转换异常(ClassCastException).
引用数据类型转换时 需注意:子类转为父类时 会造成数据的丢失
Animal t= new Tortoise(); Dog d = (Dog) t;//强制类型转换,编译阶段不报错的 // (注意:有继承或者实现关系编译阶段可以强转,没有毛病)运行时可能出错! //数据类型转换异常(ClassCastException)
一dian小知识:
空指针异常
当某个对象为空对象时 ,并调用该对象的方法和属性时 就是会出现空指针异常
java.lang.NullPointerException 空指针异常
Java建议强制类型转换前使用instanceof关键字判断当前对象的真实类型,再进行强制转换。
作用: 就是用来判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是的话返回true,反之。
语法:
对象的变量名 instanceof 类名 表示该对象是一个什么类型的 返回一个布尔值(true 或者 false)
动态绑定: 代码在运行的时候才能确定被传入的实际类型. 比如: 传入父类的引用
动态参数: 代码在运行的时候才能确定被传入的实际参数 比如: 参数是父类的数据类型代码演示(测试类):父类与子类如上。
/** 目标:学习多态形式下的类中转换机制 */ public class Test { public static void main(String[] args) { //自动类型转换: Animal a = new Dog(); a.run(); //强制类型转换: Animal a2 = new Tortoise(); a2.run(); //Tortoise t = (Tortoise) a2;//从父类类型到子类类型,必须强制类型转换 //格式:子类 对象变量 = (子类)父类类型的变量 //t.layEggs(); //Dog d = (Dog) a2;//强制类型转换,编译阶段不报错的 // (注意:有继承或者实现关系编译阶段可以强转,没有毛病)运行时可能出错! //数据类型转换异常(ClassCastException) // java建议强转转换前使用instanceof判断当前对象的真实类型,再进行强制转换 //变量名instanceof真实类型 //判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之 if (a2 instanceof Tortoise){ Tortoise t = (Tortoise) a2; //从父类类型到子类类型:必须强制类型转换 t.layEggs(); }else if (a2 instanceof Dog){ Dog d = new Dog(); d.lookDoor(); } System.out.println("--------------------"); go(new Dog()); go(new Tortoise()); } //实际开发中,在方法体里面,传递参数是用的Animal public static void go(Animal a){ a.run(); //a 它到底是乌龟还是狗,并不确定,所以必须加这段逻辑判断 if (a instanceof Tortoise){ Tortoise t = (Tortoise) a; //从父类类型到子类类型:必须强制类型转换 t.layEggs(); }else if (a instanceof Dog){ Dog d = new Dog(); d.lookDoor(); } } }
多态的综合案例
需求:
- 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备。
- 鼠标:被安装时可以完成接入,调用点击功能、拔出功能。
- 键盘:被安装时可以完成接入,调用打字功能、拔出功能。
分析:
- 定义一个USB接口(申明USB设备的规范必须是:可以接入和拔出)。
- 定义两个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有的功能。
- 创建电脑对象,创建2个USB实现类对象,分别安装到电脑里并触发功能的执行。
代码实现:
USB接口:
/** * USB接口 == 规范 */ public interface USB { //接入 拔出 void connect(); void unconnect(); }
两个实现类(键盘与鼠标):
/** 实现类:键盘 */ public class KeyBoard implements USB{ private String name ; public KeyBoard() { } public KeyBoard(String name) { this.name = name; } /** * 重写USB接入方法 */ @Override public void connect() { System.out.println(name + "成功链接了电脑--"); } /** 独有功能:按键 */ public void KeyDown(){ System.out.println(name + "敲击了:来了老弟,666----没毛病----"); } /** * 重写USB拔出方法 */ @Override public void unconnect() { System.out.println(name + "成功从电脑中拔出了--"); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
/** 实现类:鼠标 */ public class Mouse implements USB { private String name; public Mouse() { } public Mouse(String name) { this.name = name; } /** * 重写USB接入方法 */ @Override public void connect() { System.out.println(name + "成功链接了电脑--"); } /** * 独有功能:点击 */ public void dbClick() { System.out.println(name + "双击点亮小红心,一键三连----"); } /** * 重写USB拔出方法 */ @Override public void unconnect() { System.out.println(name + "成功从电脑中拔出了--"); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
电脑类:
public class Computer { private String name; public Computer() { } public Computer(String name) { this.name = name; } /** * 电脑开机的功能 */ public void start(){ System.out.println(name + "开机了---"); } /** 提供安装USB设备的入口。 */ public void installUSB(USB usb){ //多态:usb == 可能是鼠标,也可能是键盘 //接入功能 usb.connect(); //独有功能:先判断,再强转。 if (usb instanceof KeyBoard){ KeyBoard k = (KeyBoard) usb; k.KeyDown(); }else if (usb instanceof Mouse){ Mouse m = (Mouse) usb; m.dbClick(); } //拔出功能 usb.unconnect(); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
测试类:
/** 目标:USB设备模拟 1、定义USB接口:接入 拔出 2、定义两个USB的实现类:鼠标、键盘。 3、创建一个电脑对象,创建USB设备对象,安装启动 */ public class Test { public static void main(String[] args) { //a、创建电脑对象 Computer c = new Computer("外星人"); c.start();//外星人开机了--- //b、创建鼠标对象,键盘对象 USB u = new Mouse("罗技"); USB u1 = new KeyBoard("双飞燕"); c.installUSB(u);//罗技成功链接了电脑-- //罗技双击点亮小红心,一键三连---- //罗技成功从电脑中拔出了-- c.installUSB(u1);//双飞燕成功链接了电脑-- //双飞燕敲击了:来了老弟,666----没毛病---- //双飞燕成功从电脑中拔出了-- } }
多态总结
相同的行为,不同的实现
通过这句话,我们要掌握的内容:
1、多态是形容行为的,也就是”方法“,没有属性多态这么一说;
2、相同的行为指的是同名的方法;也就是说所有的多态方法至少要有一个特征,那就是方法名相同。
3、当然,我们在Java里特指的多态是两种:静态多态和动态多态。
首先明确这里的“静态”和static没有关系。这里的动和静是指程序在运行(运行期)还是没有运行(编译期)。
所谓”静态多态“指的是在编译期我们就能够确定程序运行的最终效果,即我们就能确定程序到底要执行的是哪个方法。
在语法上,重载是静态的,但是单独使用重写也是静态的。
”动态多态“是由两个技术共同组成的:重写和动态绑定技术。所以重写本身并不是动态的,动态效果是由动态绑定技术带来的。
这里需要强调一下“动态绑定技术”,它的核心归结于一个语法——父类的引用可以指向子类的对象。其实说白了,就是”=“两边的数据类型可以不一致。
我们先归纳一下基本数据类型的情况
double d = 3.14;//同类型数据值赋给同类型的变量 double d = 3;//小类型的数据值赋值给大类型的变量 int i = 3.14;//报错!!大类型的数据值赋给了小类型的变量 int i = (int) 3.14;//强转,有风险,风险是精度的丢失总结一下:
本类型的值赋给本类型的变量,成功;
小类型的值赋给大类型的变量,成功;
大类型的值赋给小类型的变量,失败。解决手段是强制转换,风险是精度的丢失。
那么到了引用数据类型也是一样的
类型 A a = new 类型A();//成功 类型 A a = new 子类型B();//成功 子类型B b = new 类型A();//报错 子类型B b = (B)new 类型A();//强制,有风险,风险是编译通过了但是运行会报异常为什么父类引用可以指向子类对象?
1、从场景上来说,父类的范围是大于子类的,既然这个变量是父类类型,子类和父类又是is-a关系,所以当然子类对象也是父类类型的一种了。
2、从内存上来说,子类对象的内部其实包含了一个完整的父类对象部分;这一点是Java继承的内部实现手段——”内存叠加“。
为什么子类引用不能指向父类对象?
1、场景上,父类范围大于子类,所以子类的变量是小类型的转不下大类型范围的数据;
2、从内存上,钱对象的内容中是没有子类人民币的特有属性和行为的,所以盒子变量会发现它找不到一个完整的人民币对象在内存中。
动态绑定技术的核心就是:”父类引用可以指向子类对象“;那么当我们声明一个父类变量的时候,它可以是A子类对象,也可以是B子类对象。只要运行起来以后,才能够确定这个父类引用到底指向了哪个子类对象!!绑定就是绑定这个!!
然后不同子类对象通过重写可能对同一个方法有着不同的实现,所以当它指向不同的子类对象的时候,调用同一个方法才有不同的效果。
不管你如何强转,如果要编译成功运行也成功,那么只有两种情况:本类指向本类,父类指向子类。
动态绑定的主要应用:
1、动态参数
public void method(父类 参数名){}
2、动态集合
父类[]集合 = new 父类[5];
集合[i] = 子类对象;
内部类是我们学到的可以在一个类里面书写的第五个内容。
内部类本身在Java当中也属于高阶用法。
首先建议第一个概念:内部类也是一个独立且完整的类,它可以有属性、构造、行为、初始化块、甚至还有内部类。所有的内部类在编译之后,都会有自己独立的class文件。只不过它的class文件的名字不在只是类名而已。
内部类概述:
- 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。
内部的作用: 它可以独立的继承某个类或者实现某个接口.
不会受到外部类的继承和实现接口的影响.
内部类可以非常优雅的解决java中单继承的问题
public class People{ //内部类 publiv class Heart{ } }
内部类的使用场景:
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
- 内部类通常可以方便访问外部类的成员,包括私有的成员。
- 内部类提供了更好的封装性,内部类本身就可以用private protected等修饰,封装性可以做更多控制。
使用内部类如何解决多继承?
public class ChildBean extends Father { public class InnerClass extends Mother{ } }
内部类之一:静态内部类(了解)
- 有static修饰,属于外部类本身的。
- 使用场景:如果一个类包含了一个完整的成分。如:汽车类中的发动机类。
- 它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
public class Outer{ //静态成员内部类 public static class Inner{ } }
静态内部类创建对象的格式:
格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器();
范例:Outer.Inner in = new Outer.Inner();
注意(静态内部类的访问拓展):
- 静态内部类中可以直接访问外部类的静态成员。
- 不能直接访问外部类的实例成员。(外部类的实例成员必须用外部类对象访问)
代码演示:
/** 外部类 */ public class Outer { public static int a = 100; private String hobby = "打篮球。"; /** 学习静态成员内部类:有static修饰,属于外部类本身 */ public static class Inner{ private String name; private int age; public static String schoolName ; public void show(){ System.out.println("名称:" + name); System.out.println(a);//静态内部类可以直接访问外部类的静态成员 //System.out.println(hobby); //报错//不能直接访问外部类的实例成员。 Outer o = new Outer(); System.out.println(o.hobby); } public Inner() { } public Inner(String name, int age) { this.name = name; this.age = age; } 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 static String getSchoolName() { return schoolName; } public static void setSchoolName(String schoolName) { Inner.schoolName = schoolName; } } }
/** 静态内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类名.内部类构造器 静态内部类中可以直接访问外部类的静态成员。不能直接访问外部类的实例成员。 */ public class Test { public static void main(String[] args) { Outer.Inner in = new Outer.Inner(); in.setName("张三"); in.show(); //名称:张三 //100 //打篮球。 } }
内部类之二:成员内部类(非静态内部类)(了解)
成员内部类 的创建需要依赖于外部类
静态内部类 是不需要依赖外部类的
- 无static修饰,属于外部类的对象。
- JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员了。
public class Outer{ //成员内部类 public class Inner{ } }
静态内部类创建对象的格式:
格式:外部类名.内部类名 对象名 = new 外部类名.new 内部类构造器();
范例:Outer.Inner in = new Outer.new Inner();
注意(成员内部类的访问拓展):
- 成员内部类可以直接访问外部类的静态成员。
- 成员内部类的实例方法中可以直接访问外部类的实例成员。(因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员。)
代码演示:
/** 外部类 */ public class Outer { public static int num = 111; private String hobby; public Outer() { } public Outer(String hobby) { this.hobby = hobby; } /** 成员内部类:不能加static修饰,属于外部类对象 可以直接访问外部类的静态成员,实例方法中可以直接访问外部类的实例成员 */ public class Inner{ private String name; private int age ; //public static int a ;//JDK16开始,成员内部类中开始支持静态成员了 // public static void test(){ // System.out.println(a); // } public Inner() { } public void show (){ System.out.println("名称:" + name); //成员内部类可以直接访问外部类的静态成员。 System.out.println("数量:" + num); //成员内部类的实例方法中可以直接访问外部类的实例成员。 System.out.println("爱好:" + hobby); } public Inner(String name, int age) { this.name = name; this.age = age; } 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; } } }
/** 成员内部类创建对象的格式:外部类名.内部类名 对象名 = new 外部类构造器 . new内部类构造器(); 成员内部类可以直接访问外部类的静态成员。成员内部类的实例方法中可以直接访问外部类的实例成员。 */ public class Test { public static void main(String[] args) { Outer.Inner in = new Outer().new Inner(); in.setName("张三"); in.show(); //Outer.Inner.test(); System.out.println("----------------"); Outer.Inner in1 = new Outer("爱听歌").new Inner(); in1.show(); } }
成员内部类面试题:
/** * 注意:在成员内部类中访问所在外部类对象,格式:外部类名.this */ /* 观察下面代码,写出合适的代码对应其注释要求输出的结果 */ public class Test2{ public static void main(String[] args) { People.Heart heart = new People().new Heart(); heart.show(); } } class People { private int heartbeat = 150; /** 成员内部类的成员变量 */ public class Heart{ private int heartbeat = 110; public void show(){ int heartbeat = 78; System.out.println(heartbeat); //78 System.out.println(this.heartbeat); //110 System.out.println(People.this.heartbeat); //150 } } }
内部类之三:局部内部类(鸡肋语法,了解)
- 局部内部类放在方法、代码块、构造器等执行体里。
- 局部内部类的文件名:外部类$N内部类.class。
代码演示:
/** 目标:了解局部内部类的语法 */ public class Test { public static void main(String[] args) { class Cat{ private String name; //public static int onLineNumber = 21; public String getName() { return name; } public void setName(String name) { this.name = name; } Cat cat = new Cat(); cat.setName("咖啡猫"); } } }
内部类之四:匿名内部类概述(重点)
- 本质上是一个没有名字的局部内部类,定义在方法中、代码块中等等。
- 作用:方便创建子类对象,最终目的为了简化代码编写。
- 特点:
匿名内部类是一个没有名字的内部类;
匿名内部类写出来就会产生一个匿名内部类的对象;
匿名内部类的对象类型相对于是当前new的那个的类型的子类类型。
格式:
new 类|抽象类名|或者接口名 (){ 重写方法; };
代码演示:
public class Test { public static void main(String[] args) { /* //2、使用多态去实例化老虎对象 Animal a = new Tiger(); a.run();*/ //但是匿名内部类就不这样,不需要定义子类 Animal a = new Animal(){ @Override public void run() { System.out.println("老虎跑得快---"); } }; a.run(); } } /*//1、构建过程需要写一个子类 class Tiger extends Animal{ @Override public void run() { System.out.println("老虎跑得快---"); } }*/ abstract class Animal{ public abstract void run(); }
匿名内部类常见的使用形式
总结:匿名内部类可以作为方法的实际参数进行传输。
案例:某个学校需要让老师、学生、运动员一起参加游泳比赛。
分析:1、游泳设置为接口;2、使用匿名内部类创建好学生、老师、运动员重写接口的游泳方法;3、定义一个方法让所有角色来比赛
代码演示:
/** 目标:掌握匿名内部类的使用形式 */ public class Test2 { public static void main(String[] args) { Swimming s = new Swimming() { @Override public void swim() { System.out.println("学生快乐的自由泳-"); } }; go(s); System.out.println("--------------------------"); Swimming s1 = new Swimming() { @Override public void swim() { System.out.println("老师游的贼快-----"); } }; go(s1); System.out.println("--------------------------"); go(new Swimming() { @Override public void swim() { System.out.println("运动员游的贼快啊-----"); } }); } /** 学生 老师 运动员 可以一起参加游泳比赛 */ public static void go(Swimming s){ System.out.println("开始---"); s.swim(); System.out.println("结束---"); } } /*class Student implements Swimming{ @Override public void swim() { System.out.println("学生快乐的自由泳-"); } }*/ interface Swimming{ void swim(); }
匿名内部类真实的使用场景演示
给按钮绑定点击事件:
import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** 目标:提高GUI编程 理解匿名内部类的真实使用场景 */ public class Test3 { public static void main(String[] args) { //1、创建窗口 JFrame win = new JFrame("登录界面"); JPanel panel = new JPanel();//桌布 win.add(panel); //2、创建一个按钮对象 JButton btn = new JButton("登录"); //注意:讲解匿名内部类的使用 /* btn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(win,"点我一下,说明爱我!");//提示框 } });*/ btn.addActionListener(e -> JOptionPane.showMessageDialog(win,"别说话,吻我!!")); //3、把按钮对象添加到桌布上展示 panel.add(btn); //4、展示窗口 win.setSize(400,300); win.setLocationRelativeTo(null);//居中 win.setVisible(true); } }
使用总结:
开发里不是我们主动去定义匿名内部类的,而是别人需要我们写或者我们可以写的时候才会使用。
匿名内部类的代码可以实现代码进一步的简化(回扣主题)。
内部类面试题
1、有几篇Java文件是否就有几篇class文件? 否!
2、Java文件的名字是否和class文件的名字保持一致?否!
3、Java文件和class文件的关系?包括数量的关系和命名的关系
calss文件的数量永远跟类的数目保持一致,而不是Java文件;
class文件的命名也是跟类名有关系,其中内部类有相应的规则需要绑定外部类的类名;
4、内部类的分类:静态内部类、成员内部类、局部内部类、匿名内部类
5、局部内部类(包括匿名内部类),操作所属方法的局部变量的问题。
要把它当作常量操作。
6、成员内部类和静态内部类在第三方法类中new的语法。 (小概率)
成员内部类:"外部类名.内部类名 对象名 = new 外部类名.new 内部类构造器()"
静态内部类:“外部类名.内部类名 对象名 = new 外部类名.内部类构造器();”
API-Object、Objects
Object类的作用:
- 一个类要么默认继承了Object类,要么间接继承了Object类,Object类是Java中的祖宗类。
- Object类的方法是一切子类都可以直接使用的,所以我们要学习Object的方法。
Object类的使用方法
方法名 说明 public String toString() 默认是返回当前对象在堆内存中的地址信息:类的权限名@内存地址
public Boolean equals(Object o) 默认是比较当前对象与另一个对象的地址是否相同,相同返回true,不同返回false 1、Object的toString方法:
toString存在的意义:
父类toString()方法存在的意义就是为了被子类重写,以便返回对象的内容信息,而不是地址信息!!
代码演示:
学生类:
public class Student { //extends Object{ private String name; private int age; private char sex; public Student() { } public Student(String name, int age, char sex) { this.name = name; this.age = age; this.sex = sex; } 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 char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } /** * 重写toString方法 * @return */ @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + '}'; } }
测试类:
public class Test { public static void main(String[] args) { Student s = new Student("周雄",21,'男'); // String rs = s.toString(); // System.out.println(rs); //System.out.println(s.toString()); //直接输出对象变量,默认可以省略toString()调用不写的 System.out.println(s);//Student{name='周雄', age=21, sex=男} } }
2、Object的equals方法:
equals存在的意义:
父类equals方法存在的意义就是为了被子类重写,以便子类自己来定制比较规则。
代码演示:
学生类:
public class Student { //extends Object{ private String name; private int age; private char sex; public Student() { } public Student(String name, int age, char sex) { this.name = name; this.age = age; this.sex = sex; } 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 char getSex() { return sex; } public void setSex(char sex) { this.sex = sex; } /** 自己重写equals,自己定制相等规则。 两个对象的内容一样就认为是相等的。 s.equals (s2) 比较者:s1 == this 被比较者:s2 ==》 o */ /** * 重写equals方法 * @param o * @return */ @Override public boolean equals(Object o) { //1、判断是否是同一个对象比较,如果是返回true if (this == o) return true; //2、如果o是null返回false 如果o不是学生类型返回false。 Student!= o的类型 if (o == null || this.getClass() != o.getClass()) return false; //3、说明o一定是学生类型,而且不为null Student student = (Student) o; return age == student.age && sex == student.sex && Objects.equals(name, student.name); } /** @Override public boolean equals(Object o){ //1、判断o是不是学生类型 if (o instanceof Student){ Student s2 = (Student) o;//强转 //2、判断两个学生的内容是否一样 // if (this.name.equals(s2.name) // && this.age == s2.age && this.sex == s2.sex){ // return true; // }else{ // return false; // } return this.name.equals(s2.name) && this.age == s2.age && this.sex == s2.sex; }else { //学生只能和学生比较,否则结果一定是false return false; } } */ }
测试类:
public class Test2 { public static void main(String[] args) { Student s1 = new Student("张三",19,'男'); Student s2 = new Student("张三",19,'男'); //equals 默认是比较2个对象的地址是否相同。 System.out.println(s1.equals(s2));//true System.out.println(s1 == s2);//false } }
Objects概述
- Objects类与Object还是继承关系,Objects类是从Jdk1.7开始之后才有的。
Objects类的常见方法
方法名 说明 public static boolean equals(Object a , Object b)
比较两个对象的,底层会先进行非空判断,从而可以避免空指针异常,再进行equals比较
public static boolean isNull(Object obj)
判断变量是否为null,为null返回true,反之
官方在进行字符串比较时,没有用对象自己的equals方法,而是选择了Objects的equals方法来比较两个对象。
Objects的equals方法比较的结果是一样的,但是更安全。:因为对象名称有可能是null,如果用null.equals的话会报错(空指针异常),但是用Objects.equals,内部会先进行非空校验。
代码演示:
public class Test { public static void main(String[] args) { String s1 = null; String s2 = new String("itheima"); //System.out.println(s1.equals(s2)); //留下了隐患,可能出现空指针异常 System.out.println(Objects.equals(s1, s2)); //false//更安全 结果更准确 /** 源码分析: public static boolean equals(Object a, Object b) { 判断是不是同一对象 判断a是不是为null return (a == b) || (a != null && a.equals(b)); } */ System.out.println(Objects.isNull(s1));//true System.out.println(s1 == null); //true System.out.println(Objects.isNull(s2));//false System.out.println(s2 == null); //false } }
对象进行内容比较的时候建议使用Objects提供的equals方法。
API-StringBuilder概述
- StringBuilder是一个可变的字符串类,我们可以把它看成是一个对象容器。
- 作用:提高字符串的操作效率,如拼接、修改等。
StringBuilder构造器:
名称 说明
public StringBuilder() 创建一个空白的可变的字符串对象,不包含任何内容 public StringBuilder(String str) 创建一个指定字符串内容的可变字符串对象 StringBuilder常用方法:
方法名称 说明 public StringBuilder append(任意类型) 添加数据并返回StringBuilder
public StringBuilder reverse() 将对象内容反转 public int length() 返回对象内容长度 public String toString() 提高toString()就可以实现把StringBuilder转换为String 代码演示:
/** 目标:学号使用StringBuilder操作字符串,最终还需要知道它性能好的原因。 StringBuilder是一个可变的字符串类,我们可以把它看成一个对象容器。 作用:提高字符串的操作效率,如拼接、修改等 常用方法:1、append:添加数据并返回StringBuider对象本身;2、reverse:将对象内容反转 3、length:返回对象内容长度;4、tostring :实现StringBuilder转换为String */ public class StringBuilderDemo1 { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); // 先建立一个空的 sb.append("a"); sb.append("b"); sb.append("c"); sb.append(1); sb.append(false); sb.append(3.3); sb.append("abc"); System.out.println(sb);//abc1false3.3abc StringBuilder sb1 = new StringBuilder(); //支持链式编程。 sb1.append("a").append("b").append("c").append("我爱你中国"); System.out.println(sb1);//abc我爱你中国 //反转 sb1.reverse().append("110"); System.out.println(sb1);//国中你爱我cba110 //长度 System.out.println(sb1.length());//11 //注意:StringBuilder只是拼接字符串的手段,效率好。 //最终的结果还是要恢复成String类型。 StringBuilder sb2 = new StringBuilder(); sb2.append("123").append("456"); //恢复成String String rs = sb2.toString(); check(rs);//123456 } /** * 写一个传入字符串的方法(检查字符串内容对不对) * @param data */ public static void check(String data){ System.out.println(data); } }
为什么拼接、反转字符串建议使用StringBuilder?
String:内容是不可变的、拼接字符串性能差。
StringBuilder:内容是可变的、拼接字符串性能好,代码优雅。
案例:打印整型数组内容
需求:设计一个方法,用于输出任意整型数组的内容,要求输出成如下格式:
“该数组内容为:[11,22,33,44,55]”
public class StringBuiderTest2 { public static void main(String[] args) { int[] arr1 = null ; System.out.println(toString(arr1)); int[] arr2 = {10,88,99} ; System.out.println(toString(arr2)); } /** 1、定义方法,接收任意整型数组,返回数组内容格式 */ public static String toString(int[] arr){ if (arr != null){ //2、开始拼接内容 StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]).append(i == arr.length - 1 ? "": ","); } sb.append("]"); return sb.toString(); }else{ return null; } } }
API-Math
Math类:
- 包含执行基本数字运算的方法,Math类没有提供公开的构造器。
- 如何使用类中的成员?看类的成员是否都是静态的,如果是,通过类名就可以直接调用
Math类的常用方法:
方法名 说明 public static int abs(int a) 获取参数绝对值
public static double ceil(double a) 向上取整 public static double floor(double a) 向下取整 public static int round(float a ) 四舍五入 public static int max(int a ,int b ) 获取两个int值中的较大值 public static double pow (double a , double b) 返回a的b次幂的值 public static double random() 返回值为double的随机数,范围[0.0 , 1.0] /** Math类:包含执行基本数字运算的方法,Math类没有提供公开的构造器。 如何使用类中的成员:看类的成员是否都是静态的,如果是,通过类名就可以直接调用 常用方法如下:abs:取绝对值;ceil:向上取整;floor:向下取整;round:四舍五入 pow:求指数次方;random:随机数(范围0 - 1.0);max:获取两个int值中的较大值 */ public class MathDemo { public static void main(String[] args) { //1、取绝对值:返回正数。 System.out.println(Math.abs(10)); //10 System.out.println(Math.abs(-10.3)); //10.3 //2、向上取整:5 System.out.println(Math.ceil(4.00000001));//5.0 //3、向下取整:4 System.out.println(Math.floor(4.999999999));//4.0 //4、求指数次方 System.out.println(Math.pow(2,3)); // 2 ^ 3 = 8.0 //5、四舍五入 10 System.out.println(Math.round(4.49999)); // 4 System.out.println(Math.round(4.500001)); // 5 //6、随机数 System.out.println(Math.random()); //0.0 - 1.0(包前不包后) //扩展:3-9之间的随机数 (0-6) + 3 //(0 - 1) * 7 + 3 int data = (int)(Math.random() * 7) + 3 ; System.out.println(data); //7、获取两个int值中的较大值 System.out.println(Math.max(5,10));//10 } }
API-System
System类概述
- System的功能是通用的,都是直接用类名调用即可,所以System不能实例化。
总结System
System.in——标准输入流,指的是控制台输入
System.out——标准输出流
System.err——标准错误输出流System.exit(0);——关闭JVM,其中0是关闭代码,0代表的是正常
System.arrayCopy();——数组的拷贝
System.gc();——召唤垃圾回收,进行一次垃圾收集,但是收不收,收谁,什么时候收,不受这句代码的控制。
System.currentTimeMills;——获取系统的当前时间毫秒值System类的常用方法
方法名 说明 public static void exit(int status)
终止当前运行的java虚拟机,非零表示异常终止
public static long currentTimeMillis()
返回当前系统的时间毫秒值形式 public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数)
数组拷贝 时间毫秒值:
- 计算机认为时间是有起点的,起始时间为:1970年1月日 00:00:00
- 时间毫秒值:指的是从1970-1-1 00: 00: 00 走到此刻的总的毫秒值。1s = 1000ms。
- 时间毫秒值用处:可以进行时间的计算:性能分析。
代码演示:
/** System类概述 常用方法:1、public static void exit(int status) :终止当前运行的java虚拟机,非零表示异常终止 2、public static long currentTimeMillis() :返回当前系统的时间毫秒值形式 3、public static void arraycopy(数据源数组,起始索引,目的地数组,起始索引,拷贝个数):数组拷贝 */ public class SystemDemo { public static void main(String[] args) { System.out.println("程序开始。。。"); // System.exit(0); // JVM终止! // 1s = 1000ms // 2、计算机认为时间有起源:返回1970-1-1 00: 00: 00 走到此刻的总的毫秒值:时间毫秒值。 /* long time = System.currentTimeMillis(); System.out.println(time); long startTime = System.currentTimeMillis(); //进行时间的计算:性能分析 for (int i = 0; i < 100000; i++) { System.out.println("输出: " + i); } long endTime = System.currentTimeMillis(); System.out.println((endTime - startTime)/1000.0 + "s");*/ //3、使数组拷贝(了解) /** arraycopy(Object src , int srcPos, Object dest , int destPos, int length) 参数一:被拷贝的数组 参数二:从哪个索引位置开始拷贝 参数三:复制的目标数组 参数四:粘贴位置 参数五:拷贝元素的个数 */ int[] arr1 = {10,20,30,40,50,60,70}; int[] arr2 = new int[6]; // [0,0,0,0,0,0] ==> [0,0,40,50,60,0] System.arraycopy(arr1 , 3 , arr2 , 2 , 3); System.out.println(Arrays.toString(arr2)); System.out.println("程序结束。。。。"); } }
API-BigDecimal (大数据类型)
BigDecimal作用:
- 用于解决浮点型运算精度失真的问题。
使用步骤:
- 创建对象BigDecimal封装浮点型数据(最好的方式是调用valueof方法)
public static BigDecimal valueOf (double val):包装浮点数成为BigDecimal对象。
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecima常用API
方法名 说明 public BigDecimal add (BigDecimal b )
加法 public BigDecimal subtract (BigDecimal b )
减法 public BigDecimal multiply(BigDecimal b )
乘法 public BigDecimal divide (BigDecimal b )
除法 public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式 )
除法 代码演示:
/** BigDecimal作用:用于解决浮点型运算精度失真的问题 创建对象BigDecimal封装浮点型数据(最好方法是调用方法) public static BigDecimal valueOf (double val):包装浮点数成为BigDecimal对象。 常用api:1、public BigDecimal add (BigDecimal b ) :加法 2、public BigDecimal subtract (BigDecimal b ) :减法 3、public BigDecimal multiply(BigDecimal b ) :乘法 4、public BigDecimal divide (BigDecimal b ) :除法 5、public BigDecimal divide(另一个BigDecimal对象,精确几位,舍入模式 ) :除法 */ public class BigDecimalDemo { public static void main(String[] args) { //浮点型运算的时候直接+ * / 可能会出现数据失真(精度问题)。 System.out.println(0.09 + 0.01);//0.09999999999999999 System.out.println(1.0 - 0.32);//0.6799999999999999 System.out.println(1.015 * 100);//101.49999999999999 System.out.println(1.301 / 100);//0.013009999999999999 System.out.println("-----------------"); double a = 0.1; double b = 0.2; double c = a + b; System.out.println(c);//0.30000000000000004 System.out.println("-----------------"); //包装浮点型数据成为大数据对象BigDecimal BigDecimal a1 = BigDecimal.valueOf(a);//0.1 BigDecimal b1 = BigDecimal.valueOf(b);//0.2 //BigDecimal c1 = a1.add(b1);//0.3 //加 //BigDecimal c1 = a1.subtract(b1);//-0.1 //减 //BigDecimal c1 = a1.multiply(b1); //0.02//乘 BigDecimal c1 = a1.divide(b1);//0.5 //除 System.out.println(c1); //目的:double //还需要把BigDecimal的对象转换成double double rs = c1.doubleValue(); System.out.println(rs); //注意:BigDecimal是一定要精度运算的 BigDecimal a11 = BigDecimal.valueOf(10.0); BigDecimal b11 = BigDecimal.valueOf(3.0); /* BigDecimal c10 = a11.divide(b11); System.out.println(c10);*/ //会报错崩掉 /** 参数一:除数 参数二:保留小数位数 参数三:舍入模式:HALF_UP四舍五入 */ BigDecimal c11 = a11.divide(b11 , 2 , RoundingMode.HALF_UP); System.out.println(c11);//3.33 System.out.println("------------------"); } }