JavaSE| 面向对象的三大特征
面向对象的基本特征
1、封装Encapsulation
目的:隐藏实现细节,让使用者方便,让代码更安全
将对象的属性和行为封装起来,其载体就是类。类通常对客户隐藏其实现细节,这就是封装的思想。
封装(Encapsulation):把该隐藏的隐藏起来,该暴露的暴露出来。
封装需要通过访问控制修饰符(权限修饰符)来实现。
/* 权限修饰符: 本类 其他类(本包的其他类、其他包的子类、其他包的非子类) private √ × × × (本类) 缺省(省略) √ √ × × (同包同类) protected(受保护的)√ √ √ × (同类同包子类,本类 + 本包其他类 + 其他包的子类 ) public √ √ √ √ 权限修饰符的作用:限定某个类型、成员的可访问的范围、可见性的范围 权限修饰符可以修饰什么? private:成员(属性、方法、构造器、内部类) 缺省:外部类等类型、成员(属性、方法、构造器、内部类) protected:成员(属性、方法、构造器、内部类) public:外部类等类型、成员(属性、方法、构造器、内部类) 当public修饰外部类时,要注意类名必须与源文件名一致,即一个源文件只能有一个外部的public类 修饰符的学习:(1)可以修饰什么(2)修饰后有什么影响 */ //外部类 public class TestModifier{ //内部类 public class Inner{ } }
所有的类默认继承Object
访问权限:权利和限制; 方法的提供者和方法的调用者
Java中所谓的权限问题,其实就是对象属性或方法的提供者和调用者之间的关系(同类,同包,子类)问题
对象当中的. 是指从属关系,不是调用的意思;
// clone方法的提供者:com.atguigu.testjava.User
// 方法的调用者:com.atguigu.testjava.TestJava
这两个类都继承了1个父类Object,但它们不是子类的关系(它们两个的父类不是一个爸爸),所以虽是protected修饰的clone,若User不重写clone方法,user对象是不能调用的
package com.atguigu.testjava; public class TestJava { public static void main(String[] args) throws Exception { User user = new User(); // clone方法的提供者:com.atguigu.testjava.User // 方法的调用者:com.atguigu.testjava.TestJava //这两个类都继承了1个父类Object,但它们不是子类的关系(它们两个的父类不是一个爸爸),所以虽是protected修饰的clone,若User不重写clone方法,user对象是不能调用的 user.clone(); // } } /**User类继承Object * protected native Object clone() */ class User{ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
方法重写:JVM的动态绑定技术(https://www.cnblogs.com/shengyang17/p/10010418.html),所以对象user会去找子类重写的方法; 子类中没有就去父类中;
2、继承(inheritance)
继承:延续,保留,并且扩展 extends
目的:代码的复用和功能扩展; 继承还可以表示is-a的逻辑关系;Student is a Person. Apple is a Fruit.
2、如何继承?
【修饰符】 class 子类名 extends 父类名{
}
子类:SubClass,也称为派生类; 父类:SuperClass,也称为超类、基类
3、继承的特点
* (1)子类继承父类时,会继承父类的所有的属性、方法。* 但是因为修饰符的原因,某些属性和方法在子类中不可见。
* (2)子类“不会”继承父类的构造器;
* (3)在子类的构造器中一定会“调用”父类的构造器,并且默认调用的是父类的“无参”构造器。
如果父类没有“无参”构造,那么子类必须手动调用父类的“有参”构造。(子类在创建对象时候默认调用父类的无参构造器)
* (4)如果一个类没有显式的声明它的父类,那么它有一个默认的父类:java.lang.Object
* (5)在Java中,类的继承有“单继承限制”;意思:每一个子类只能有一个直接的父类,类似于每个人只有一个亲生父亲;makes code more reliable.
* (6)Java中支持多层继承,即父类仍然可以有父类,子类会继承所有父类的属性和方法。 意思:代代相传
* (7)一个父类却同时可以有很多的子类,而且子类还可以有很多子类;* 意思:子孙满堂
* (8)子类可以扩展父类没有的属性和方法
方法的重写:Override * 方法的重写:当子类继承了父类的方法,但是父类的某个方法的方法体实现不适合于子类,那么我们子类可以选择进行重写。 要求: (1)子类重写父类的方法,方法名必须一样;2)子类重写父类的方法,形参列表必须一样 (3)子类重写父类的方法,返回值类型有要求: 如果返回值类型是基本数据类型和void,那么要求必须完全一致; 如果返回值类型是引用数据类型,那么要求<=的关系 (4)子类重写父类的方法,权限修饰符的范围要求>=的关系;
(5)抛出的异常列表的类型:<= 子类重写方法抛出的异常类型 <= 父类被重写方法抛出的异常类型
在子类中,如果要调用父类被重写的方法,可以使用“super. "被重写方法”
...略 public String getInfo(){ return "姓名:" + name + "\t薪资:" + salary; } public int test(){ return 0; } public Object method(){ return null; } protected void function(){ } } class Manager extends Emplyee{ private double bonus; /*public String getInfo(){ return "姓名:"getName() + "\t薪资:" + getSalary() + "\t奖金:" +bonus; }*/ public String getInfo(){ return super.getInfo() + "\t奖金:" +bonus; } public String method(){ //String < Object 类型 return null; } public void function(){ } }
Java中方法重写是基于JVM的动态绑定技术:调用对象的成员方法(直接or间接都可以)时,JVM会将对象的实际内存和当前的方法进行绑定。
成员变量没有动态绑定操作,成员变量的调用是在哪里声明在哪里使用
//40 调用a对象的成员方法,JVM会将对象的实际内存(实际内存是new B())和当前的方法-实际内存中的方法getResult()进行绑定。这是是有两个一模一样的变量 i的,那么它-对象a为什么调用自己的i了呢,因为省略了this.i,如果要调用父类的i就要super.i
public class TestOverride { public static void main(String[] args) { A a = new B(); //多态 System.out.println(a.getResult()); //40 调用a对象的成员方法,JVM会将对象的实际内存(实际内存是new B())和当前的方法getResult()进行绑定。 } } class A { public int i = 10; public int getResult() { return i + 10; } } class B extends A { public int i = 20; public int getResult() { return i + 20; } // }
//30 如果把当前对象实际内存中的方法注释掉,则它就会去找父类中的 getResult( )方法---是成员变量,没有动态绑定操作;这时是只有一个变量 i;
public class TestOverride { public static void main(String[] args) { A a = new B(); //多态 System.out.println(a.getResult()); } } class A { public int i = 10; public int getResult() { return i + 20; } } class B extends A { public int i = 20; /* public int getResult() { return this.i + 20; }*/ // }
//40 调用对象的成员方法,对象new B()中实际内存和当前方法即B类中方法getResult(),B类中没有重写它是继承父类中getResult,父类中的getInt()也是属于调用对象的成员方法,它会在B类中找这个方法(动态绑定)
public class TestOverride { public static void main(String[] args) { A a = new B(); //多态 System.out.println(a.getResult()); } } class A { public int i = 10; public int getResult() { return getInt() + 20; } public int getInt(){ return i; } } class B extends A { public int i = 20; /* public int getResult() { return this.i + 20; }*/ // public int getInt(){ return i; } }
基于动态绑定---->模板方法设计模式--即上边写的代码; HttpServlet也是这种模式;父类把方法业务的骨架搭建好,提供模板,把结果返回,子类重写模板的细节;
* this关键字:当前对象 * (1)this.属性 * 当成员变量与局部变量重名时,在成员变量前面加"this." * * (2)this.方法 * 表示调用当前对象的成员方法,可以省略this. * * (3)this()或this(实参列表) 表示访问本类的其他构造器; * this()或this(实参列表); this()调用本类的无参构造。 * * super关键字:父类的 * 前提:要通过super调用父类的属性、方法、构造器,必须要求他们的可见性是在子类中可见的。 * * (1)super.属性 * 当子类的属性与父类的属性重名时,可以使用super.属性 表示父类的属性。 * * (2)super.方法 * 当子类重写了父类的方法,又想调用父类被重写的方法,那么可以使用“super.父类的方法名 被重写方法” * * (3)super()或super(实参列表) * super()表示调用父类的无参构造器,可以省略。 子类继承父类(子类、父类两者都有无参构造和有参构造,子类在进行创建对象时,不管有没有传参,
都是默认调用父类的无参构造;如果是传参数了,它只是传给了它自己的构造器。
super(实参列表)表示调用父类的有参构造,不可以省略,特别是父类没有无参构造时
* 要求:和this()、this(实参列表)一样,必须在构造器的首行。
public Manager() { super(); } public Manager(String name, double salary, double bonus) { super(name, salary); //调用父类的有参构造 加上 子类自己扩展的属性。 this.bonus = bonus; } if(amount < balance){ //正常取 super.withdraw(amount); //调用父类的withdraw方法;方法的重写。
public class Test { public static void main(String[] args) { Base b1 = new Base();//本态引用,只看Base类,执行了Base类的无参构造,在无参构造中,调用了Base类的method(100) Base b2 = new Sub(); //执行的是子类重写的方法。 //多态引用,创建的是子类的对象,执行子类Sub的无参构造; //在子类的构造器中,一定会调用父类的构造器,默认调用的是父类的无参构造,会导致父类的无参构造被执行,因为父类的无参构造中调用method(100), //它省略了“this.”,这个this是当前对象,当前正在创建的是子类Sub的对象,执行的是子类Sub重写的method(100) //接着在子类的构造器中,有super.method(70),这个执行的是父类的method(70),所以会打印 Base:70 } } class Base{ Base(){ method(100); //省略了this.method(100),this指当前对象 } public void method(int i){ System.out.println("base : " + i); // 1.base: 100; 3. base: 70 } } class Sub extends Base{ Sub(){ //省略了super(); super.method(70); //super.方法。子类重写了method方法,但是它又想调用父类的method方法了。 } public void method(int j){ //子类重写了method方法。 System.out.println("sub : " + j); //2. sub: 100 ; } } --->> base : 100 sub : 100 base : 70
this的追溯不仅限于当前类,也可以是从父类继承的属性、方法,只要可见。 * 如果子类中有和父类一样的属性,那么this.就代表子类的, * 如果子类重写的父类的方法,那么this.就代表子类的。 * this表示当前对象 “运行时的类型” * (1)在构造器中,this代表的是正在new的那个对象 * (2)在成员方法中,this代表的是调用该方法的那个对象 * this,先从当前类中开始找 * (1)一种如果this代表子类的对象,那么还要检查子类是否重写 * (2)另一种如果this代表的是本类的对象,那么直接从本类中查找 * super,先从直接父类开始找,如果直接父类没有,一直往上
public class TestThis2 { public static void main(String[] args) { //Father f = new Son();//这句代码在new子类Son的对象,所以这个this代表子类的对象 //子类的对象执行test()一定是子类重写的代码 //Father f2 = new Father();//这句代码在new父类的对象,所以这个this代表父类的对象 Father f3 = new Son(); f3.method();//f3运行时,代表的是子类的对象 } } class Father{ //(1)父类的无参构造会不会执行?会,因为子类的默认无参构造中默认调用父类的无参构造super() public Father(){ //(2)这个test()执行的是哪个? test();//等价于this.test() } public void test(){ System.out.println("父类的test()方法"); } public void method(){ System.out.println("父类的method()方法"); } } class Son extends Father{ public void test(){ System.out.println("子类的test()方法"); } public void method(){ System.out.println("子类的method()方法"); } }
public class TestSuper { public static void main(String[] args) { ErZi r = new ErZi(); } } class ZuZong{ public void test(){ System.out.println("祖宗的test()"); } } class YeYe extends ZuZong{ } class BaBa extends YeYe{ public void test(){ System.out.println("爸爸的test()"); } } class ErZi extends BaBa{ ErZi(){ super.test(); } public void test(){ System.out.println("儿子test()"); } }
public class TestThis { } class Base{ public void test(){ System.out.println("父类的test()"); } public void funtion(){ System.out.println("父类的funtion()"); } } class Sub extends Base{ public void method(){ System.out.println("子类的method()"); //this.test(); //this对象它有从Base继承的test()和Sub类自己声明的method() test();//省略的this. this.function();//代表子类自己的 } //重写 public void function(){ System.out.println("子类的funtion()"); } }
3、多态(Polymorphism)
多种形态,目的是使代码更灵活,功能更丰富。
* 如何理解它? 针对方法 * 1、方法的重载(一个类中一个方法功能的多种表现形式)与重写(父子类对于同一个方法表现出不同的形式):一个功能有多种形式 * public static int getMax(int x, int y) * public static int getMax(int x, int y,int z) * public static double getMax(double x, double z) * 功能都是找最大值,它有多种形式 * public class Employee{ * public String getInfo(){ * .... * } * } * public class Manager extends Employee{ * public String getInfo(){ * .... + 奖金 * } * } * 父子类中该方法都是返回对象的详细信息,但是父子类中有两种形式 * * 2、对象的多态性 * 某个对象在Java中 1)编译时类型(编辑代码且javac.exe); 2)运行时类型,不一致(java.exe) --->>多态 父类的引用指向子类的对象 ** 前提条件: * (1)类有继承关系 * (2)方法的重写 * (3)多态引用:父类的变量指向子类的对象 * Person p = new Man(); * 出现的对象多态性的现象: * 编译时按照父类的类型编译,运行时按照子类的类型运行,执行的方法是子类重写父类的方法体。 * */属性没有多态,多态是针对方法的。。。
public class Exam2 { public static void main(String[] args) { Base b = new Sub(); //多态引用; System.out.println(b.x); //1 -->属性没有多态,按照编译时,b对象有1个x,是Base类中的。 } } class Base{ int x = 1; } class Sub extends Base{ int x = 2; }
public class TestPolymorphism { public static void main(String[] args) { Person p = new Man(); //编译时类型是Person,运行时类型是Man p.eat(); //运行时候是Man类型; //p.smoke();//编译时报错,因为p在编译时按照Person类型,Person类型中没有smoke()方法 System.out.println(p.name); p.sleep(); //Man m = (Man) new Person(); //发生异常java.lang.ClassCastException Person pe = new Man(); //前提父类原先指向的就是本身对象才能向下转型成功, //如果父类原先指向的是其他类型(父类的,兄弟类的)的对象 Man m1 = (Man)pe; m1.smoke(); System.out.println(m1.name); } } class Person{ String name = "父类"; int age = 100; public void eat(){ System.out.println("吃吃吃饭"); } public void sleep(){ System.out.println("睡觉觉"); } } class Man extends Person{ String name = "子类男人"; int age = 20; /*public void eat(){ System.out.println("狼吞虎咽"); }*/ public void sleep(){ System.out.println("呼呼大睡"); } public void smoke(){ System.out.println("吞云吐雾"); } } class Son extends Man{ @Override public void eat() { System.out.println("儿子吃"); } @Override public void sleep() { System.out.println("儿子睡觉"); } @Override public void smoke() { System.out.println("儿子吸烟"); } }
public class TestPolymorphism { public static void main(String[] args) { // Object obj = new String("hello"); // 此时的obj对象就有两种类型,编译时类型是Object类型,运行时是String类型 } }
//本态引用:编译时类型和运行时类型是一样的 ; VS 多态引用:父类的变量指向子类的对象 如 Person p = new Man(); // Person p = new Person(); // Man m = new Man(); // Girl g = new Girl();
* 多态的第一个应用:多态数组 * 元素的类型是父类的类型,元素存的是子类的对象 * * 需求:用一个数组来存储多个图形对象,这里面可能有圆对象,可能有矩形对象,统一管理他们,要显示他们的面积、周长、向下信息等,甚至按照面积排序... * 1、声明一个Circle类,有半径radius,有求面积的方法,求周长的方法,返回详细信息的方法 * 2、声明一个Rectangle矩形类,有长和宽,有求面积的方法,求周长的方法,返回详细信息的方法 * 3、声明一个父类Graphic图形类,让Circle和Rectangle继承它 * * 类:一类具有相同特性的事物的抽象描述。
public class TestUse1 { public static void main(String[] args) { //多态引用,左边的arr[]是父类的类型Graphic,右边赋值的是子类的对象; Graphic[] arr = new Graphic[3]; //数组的声明和初始化 arr[0] = new Circle(2); arr[1] = new Ractangle(3,2); arr[2] = new Circle(6.1); //编译时g按照Graphic的父类类型编译,执行的时候,执行的是子类重写的方法体 for(Graphic num: arr){ System.out.println(num.getInfo() + "\n"); //System.out.println(num.getArea() + "\t"); } } }
public class TestUse1 { public static void main(String[] args) { //多态引用,左边的arr[]是父类的类型Graphic,右边赋值的是子类的对象; Graphic[] arr = new Graphic[3]; //数组的声明和初始化 arr[0] = new Circle(2); arr[1] = new Ractangle(3,2); arr[2] = new Circle(6.1); //编译时g按照Graphic的父类类型编译,执行的时候,执行的是子类重写的方法体 for(Graphic num: arr){ System.out.println(num.getInfo() + "\n"); //System.out.println(num.getArea() + "\t"); } } } package com.atguigu.variable; public class Graphic { public double getArea(){ return 0.0; } public double getPrimeter(){ return 0.0; } public String getInfo(){ return ""; } } package com.atguigu.variable; public class Circle extends Graphic{ private double radius; public Circle() { super(); } public Circle(double radius) { super(); this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public double getArea(){ return Math.PI * radius * radius; } public double getPrimeter(){ return 2 * Math.PI * radius; } public String getInfo(){ return "半径" + radius + "\t面积" + getArea() + "\t周长" + getPrimeter(); } } package com.atguigu.variable; public class Ractangle extends Graphic{ private double length; private double width; public Ractangle() { super(); } public Ractangle(double length, double width) { super(); this.length = length; this.width = width; } public double getLength() { return length; } public void setLength(double length) { this.length = length; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getArea(){ return length * width; } public double getPrimeter(){ return 2 * (length + width); } public String getInfo(){ return "长" + length +"\t宽:"+ width + "\t面积" + getArea() + "\t周长" + getPrimeter(); } }
* 多态的应用之二:多态参数 * 形参是:父类类型 * 实参是:子类的对象 * * 需求: * 1、声明一个Circle类,有半径radius,有求面积的方法 * 2、声明一个Rectangle矩形类,有长和宽,有求面积的方法 * 3、声明一个Triangle三角形,有三边,有求面积的方法 * 3、在测试类中,声明一个方法,功能:可以比较任意两个图形对象的面积,是否相等
public class TestUse2 { public static void main(String[] args) { Circle c = new Circle(2); Ractangle r = new Ractangle(3, 2); //实参给形参赋值 //形参的类型:Graphic,实参的类型:一个是Circle,一个是Rectangle //隐含了:Graphic g1 = c;//多态引用 //隐含了:Graphic g2 = r;//多态引用 boolean result = equalsGraphic(c, r); if(result){ System.out.println("面积相等"); }else{ System.out.println("面积不相等"); } } //可以比较任意两个图形对象的面积,是否相等 //返回值类型:boolean //形参?两个图形类型 public static boolean equalsGraphic(Graphic g1, Graphic g2){ //g1和g2编译时按照Graphic类型,运行时g1按照Circle,g2按照Rectangle类型 if(g1.getArea() == g2.getArea()){ return true; }else{ return false; } } }
向上转型和向下转型
* 基本数据类型:byte,short,int,long,float,double,boolean,char * 1、自动类型转换 * byte,short,char->int->long->float->double * boolean不参与 * .... * * 2、强制类型转换 * double->float->long->int->char,byte,short * 强制类型转换需要(),可能损失精度或溢出 * boolean不参与 * .... * * 父子类之间类型转换:
1、向上转型:自动完成 子类 转 父类 * Person p = new Man();//一个Man的对象在编译期间向上转型为Person类型
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,即向上转型;那么
该变量就不能再访问子类中添加的属性和方法,只能访问子类中重写父类的方法 和 父类中的属性、方法(子类中没有父类中有的方法也可以调用);
重写形参要一模一样,如果形参不一样,要看这个参数是否符合(父类的引用变量可以接收子类的对象)见习题。
如果没有重写,只是本态引用,则子类的变量只能执行它自己和继承到父类的方法。
* Graphic[] arr = new Graphic[5]; * arr[0] = new Circle(2);//一个Circle对象在编译期间向上转型为Graphic类型 * 2、向下转型:强制完成
Man m = (Man) new Person(); //发生异常java.lang.ClassCastException
Person pe = new Man(); //前提父类原先指向的就是本身对象才能向下转型成功,如果父类原先指向的是其他类型(父类的,兄弟类的)的对象,那么就会发生java.lang.ClassCastException类型转换异常
Man m1 = (Man)pe;
m1.smoke(); //对象m1可以调用子类的所有方法和属性;不能调用父类的属性和方法。
System.out.println(m1.name);
* Person[] arr = new Person[5]; arr[0] = new Man();//向上转型 Man m = (Man) arr[0];//向下转型 --->> Man m = (Man) new Person();这样子写它会发生异常的。 * * 向下转型之前,一定发生过向上转型。 * * * 父类的变量中可以存储子类的对象, * 但是子类的变量中是不能存储父类的对象。
* 关键字:instanceof * 为了避免转型的失败,可以在转型之前加instancof判断
public class TestClassCast { public static void main(String[] args) { Person[] arr = new Person[3]; arr[0] = new Man(); arr[1] = new Girl(); arr[2] = new Person(); //希望调用Man类型的smoke()方法 //arr[0].smoke(); //编译时,arr[0]是按照父类Person类型编译的 Man m = (Man) arr[0]; //向下转型; m.smoke(); //编译没保错,因为编译时,arr[1]按照父类Person类型编译的,从Person类型向下转为Man,语法上可以 //Man m2 = (Man)arr[1];//运行时,发生java.lang.ClassCastException:类型转换异常 //m2.smoke(); //.Girl cannot be cast to .Man Man m3 = (Man)arr[2];//运行时,发生java.lang.ClassCastException:类型转换异常 m3.smoke(); //.Person cannot be cast to .Man } }
Man m -->>指向Man对象是可以的
Man m2 --->>不能指向Girl对象,因为它俩之间没有继承关系的。
Man m3 ---->>不能指向Person父对象。
public class TestClassCast { public static void main(String[] args) { Person[] arr = new Person[3]; arr[0] = new Man(); arr[1] = new Girl(); arr[2] = new Person(); for(Person p : arr){ p.eat(); //p.sleep(); if(p instanceof Man){ Man m =(Man) p; m.smoke(); }if(p instanceof Girl){ Girl g = (Girl) p; g.shopping(); } } } }
Object
public class TestObject { public static void main(String[] args) { Circle circle = new Circle(); int[] arr = new int[]{1,2,3,4}; //多态引用 Object obj = new Circle(); Object obj2 = new int[4]; Object[] all = new Object[10]; } } class Circle{ }
* java.lang.Object: * 类 Object 是类层次结构的根类。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。 * * 潜台词: * (1)所有类都是Object的子类 * 一个类如果没有显式声明它继承某个别的类,那么它的直接父类就是Object。 * (2)Object中的所有的方法、属性,在所有类中都有 * 即任意对象(包括数组对象),都可以调用Object中的方法。 * (3)所有对象在创建时,都会调用Object的无参构造方法。 看不出来 * (4)Object类型的变量,可以接受任意类型的数据 * 即Object类型的变量可以与非Object对象构成多态引用。 * 即Object类型的数组可以存储所有类型的数据。 * * * 常用方法:
* 常用方法: * (1)public final Class<?> getClass():返回此 Object 的运行时类。 * (2)public String toString():返回该对象的字符串表示。 * Object类中的toString():由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成 * 一个对象如果直接用Sysout打印 或 与字符串拼接,默认就是调用这个对象的toString()方法 * * 建议所有子类重写toString()方法,使得对象的信息更清晰。 * (3)protected void finalize()throws Throwable * 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize 方法,以配置系统资源或执行其他清除。 * (A)这个finalize()方法,由垃圾回收器调用,不是程序员手动调用 * (B)什么时候调用?当垃圾回收器确定某个对象是“垃圾”,才会调用,而且这个方法每个对象只会被调用一次。 * 更深的意思:如果这个对象的finalize()方法中,使得这个对象“复活”了,那么下次它再称为垃圾时,就不会调用finalize() * * (4)public int hashCode():返回该对象的哈希码值。(就是每一个对象有一个数字编码,这个编码表示这个对象的信息) * 理想状态下:每一个对象的hash码是唯一的。 * 现实中,可能存在两个对象的hash码是相同的。 * hash码是由散列函数根据对象的信息计算出来的。 * * 记住结论: * 如果两个对象的hash码“不相等”,那么这个两个对象一定“不是同一个对象”; * 但是如果两个对象的hash码“相等”,那么这个两个对象却不一定是“相等的对象”,可能是相同,也可能不同。 * 即不同通过hash码来确定两个对象是否“相等” * * 支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 * * (5)public boolean equals(Object obj) * 它是真正用来确定两个对象是否“相等”的唯一标准。 * * 如果某个类没有重写equals方法,那么Object中的equals方法和“==”是一样,比较的是对象的内存地址。 * 注意:String类型是重写过的equals方法,因此String类型的比较不用==,用equals
String类是final修饰的。
==和equals()的区别 ==:如果是基本数据类型,比较的是数据值 如果是引用数据类型,比较的是对象的地址值 equals():必须是对象才能调用,它是Object类中声明的,如果子类没有重写,那么它的效果和==是一样的,也是比较对象的地址。
如果子类重写了,那么就按照重写的规则进行比较,例如String类型重写了equals,比较的是字符串的字符内容。
String str = new String(); //TestFactory与String类发生耦合,依赖 //对象的使用者TestFactory不负责BMW和Aodi对象的创建,与BMW和Aodi类解耦合 Car b = Factory.getCar("宝马"); b.run(); Car a = Factory.getCar("奥迪"); a.run(); class Factory{ //工厂只负责创建对象;创建一个getCar方法来生产Car的对象 public static Car getCar(String type){ if("宝马".equals(type)){ return new BMW(); }else if("奥迪".equals(type)){ return new AoDi(); } return null; } }
重写一个类的equals方法需要主意什么?
(1)必须和hashCode()方法一起重写,凡是参与equals比较的属性,一定要参与hashCode值的计算。 (2)equals的重写要求遵循几个原则: equals 方法在非空对象引用上实现相等关系: • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。 • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。 • 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。 • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。 • 对于任何非空引用值 x,x.equals(null) 都应返回 false。
原理:
public class TestObjectMethod { public static void main(String[] args) { Object obj = "Hello"; System.out.println(obj.getClass());//class java.lang.String Student stu = new Student(1, "kris"); //com.atguigu.object.Student@15db9742 //由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成 System.out.println(stu.toString()); System.out.println(stu); String info = "学生信息" + stu; System.out.println(info); //字符串对象 System.out.println("Aaa".hashCode()); //65569 Student s1 = new Student(1,"alex"); Student s2 = new Student(1,"alex"); System.out.println(s1 == s2); //false System.out.println(s1.equals(s2)); //true System.out.println(s1.equals(s1));//true地址相同,同一个对象,自己和自己比较 System.out.println(s1.equals("hello"));//false s1的运行时是Student,"hello"的运行时类型String } } class Student{ private int id; private String name; public Student() { super(); } public Student(int id, String name) { super(); this.id = id; this.name = name; } 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 "Student [id=" + id + ", name=" + name + "]"; } @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; } //System.out.println(s1.equals(s2)); //s1是this,调用equals的对象,就是this //s2是实参,给obj形参赋值 @Override public boolean equals(Object obj) { //如果s1和s2的地址是一样,就返回true if (this == obj) return true; //obj为null,就返回false,因为this现在一定不是null,因为如果this是null早就空指针异常了,进不来 if (obj == null) return false; //this的运行时类型和obj的运行时类型是否一样 if (getClass() != obj.getClass()) return false; //this和obj的运行时类型是一样,那么obj也是Student对象 /* if(this.id != obj.id){//因为obj的编译时类型是Object }*/ Student other = (Student) obj; if (id != other.id) return false; //学号是一样 if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name))//这个equals是String,因为name是String类型 return false; return true; } }
finalize()方法,由垃圾回收器调用;原理如下
* Alt +Shift +S,选择Override.... */ //这个finalize()方法,由垃圾回收器调用 public class TestFinalize { public static void main(String[] args) { for (int i = 1; i <=10; i++) { //my是属于for循环的循环体的局部变量 //每一次循环,这个my都是全新的my,意味着上一次的my指向的对象,就称为了垃圾 MyClass my = new MyClass(); System.out.println("my = " + my); } //gc:Garbage Collector System.gc();//通知垃圾回收器来回收垃圾 try { //让程序慢点退出,1秒后再退出 Thread.sleep(1000);//单位是毫秒,1s = 1000ms } catch (InterruptedException e) { e.printStackTrace(); } } } class MyClass{ @Override protected void finalize() throws Throwable { System.out.println("轻轻的我走了,正如我轻轻的来.."); } }
抽象类
* 抽象类: * 当某个父类,需要体现它的子类们的共同的特性,例如,共同的方法,但是这个方法,在父类中无法给出合理的实现, * 具体的实现应该有子类来完成。那么,这样的方法我们可以设计为抽象方法。包含抽象方法的类,必须是抽象类。 * * 一、抽象方法 * 【修饰符】 abstract 返回值类型 方法名(【形参列表】); * * 注意: * (1)抽象方法必须有abstract修饰????????????? * (2)抽象方法没有方法体{} * (3)抽象方法必须在抽象类、接口中 * 换句话说,如果某个类包含抽象方法,这个类必须是一个抽象类。 * * 二、抽象类 * 【权限修饰符】 abstract class 抽象类名{ * } * * 抽象类的特点: * (1)抽象类不能实例化,即抽象类不能直接创建对象 * (2)抽象类就是用来被继承的,子类继承抽象类时,必须重写(实现)抽象方法,否则这个子类也得是一个抽象类 * (3)包含抽象方法的类,必须是抽象类,但是反过来,抽象类可以没有抽象方法。 * 如果一个抽象类,没有抽象方法,这样的设计的目的,就是为了不让你创建它的对象,让你创建它的子类的对象。 * * * 抽象类的成员? * (1)属性 * (2)非抽象方法 * (3)代码块:给属性初始化 * (4)构造器:给子类调用 * * 比较:普通类与抽象类的区别 * (1)抽象类有abstract修饰,普通类没有 * (2)抽象类可以有抽象方法,普通类不能有 * (3)抽象类不能实例化,普通类可以 *
public class TestAbstract { public static void main(String[] args) { // Graphic g = new Graphic();//不能创建抽象类对象 Graphic g1 = new Circle(1.2);//多态引用,创建的是子类的对象 System.out.println("面积:" + g1.getArea()); } } abstract class Graphic{ private static String info; private String name; public Graphic() { super(); } public Graphic(String name) { super(); this.name = name; } public static String getInfo() { return info; } public static void setInfo(String info) { Graphic.info = info; } public String getName() { return name; } public void setName(String name) { this.name = name; } public abstract double getArea(); } class Circle extends Graphic{ private double radius; public Circle(double radius) { super(); this.radius = radius; } public double getArea(){ return Math.PI * radius * radius; } } class Rectangle extends Graphic{ private double length; private double width; @Override public double getArea() { return length * width; } } abstract class Person{ } class Man extends Person{ }
设计模式
* 设计模式:解决某些问题,形成的代码的套路。 * 常见的设计模式一共有23种。我们在SE阶段会介绍一小部分,但是因为设计模式比较难理解,对于初级程序员来说,特别难。 * SE阶段,把设计模式的要求定为了解。除了一个设计模式需要掌握,并且能够百分百手写的,是单例。 * * 抽象类有一个应用:模板设计模式(了解,认识) * * 当解决某个问题时,它的整体的算法结构,步骤是确定的,只是其中的某个小步骤是不确定的,由使用者来确定。 * 遇到这种情况,就需要用到模板设计模式。 * 我们把能确定部分先完成,不能确定的部分,抽象成一个抽象方法,让使用者去实现它。 * * 例如:要你写一个功能,它可以计算任意一段代码的运行时间 * 算法,步骤: * 1、先获取开始时间 * 2、执行xx代码 * 3、获取结束时间 * 4、计算时间差 * 这里时间用毫秒表示,用long类型 * * 提示: * System.currentTimeMillis():可以返回当前系统时间距离1970年1月1日 0点0分0秒0毫秒的毫秒值 */ public class TestTemplate { public static void main(String[] args) { MyCalTime my = new MyCalTime(); long time = my.getTime(); System.out.println("运行时间:" + time); } } //这个类就是模板类 abstract class CalTime{ //如果不希望子类重写getTime()方法,修改算法结构,可以用final修饰 public final long getTime(){ long start = System.currentTimeMillis(); doWork(); long end = System.currentTimeMillis(); return end - start; } public abstract void doWork(); } //我需要计算从1加到100000的和以及运行时间 class MyCalTime extends CalTime{ @Override public void doWork() { long sum = 0; for (int i = 1; i <= 100000; i++) { sum += i; } System.out.println("和:" + sum); } } //我需要计算复制一个文件的时间 class FileCopyCalTime extends CalTime{ @Override public void doWork() { //复制文件功能 } }