继承让我们更加容易实现类的扩展。
extends的意思是“扩展”,子类是父类的扩展。
在编程中,如果新定义一个Student类,发现已经有Person类包含了我们需要的属性和方法,
那么Student类只需要继承Person类即可拥有Person类的属性和方法。
使用extends实现继承
public class Test{ public static void main(String[] args) { Student s = new Student("高淇",172,"Java"); s.rest(); s.study(); } } class Person { String name; int height; public void rest(){ System.out.println("休息一会!"); } } class Student extends Person { String major; //专业 public void study(){ System.out.println("在尚学堂,学习Java"); } public Student(String name,int height,String major) { //天然拥有父类的属性 this.name = name; this.height = height; this.major = major; } }继承使用要点
1.父类也称作超类、基类、派生类等。
2.Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承链过于复杂,系统难于维护。
3.Java中类没有多继承,接口有多继承。
4.子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。
5.如果定义一个类时,没有调用extends,则它的父类是:java.lang.Object。
instanceof运算符
instanceof是二元运算符,左边是对象,右边是类;当对象是右面类或子类所创建对象时,返回true;否则,返回false。
使用instanceof运算符进行类型判断
public static void main(String[] args) { Student s = new Student("高淇",172,"Java"); System.out.println(s instanceof Person);//true System.out.println(s instanceof Student);//true }方法的重写override
子类通过重写父类的方法,可以用自身的行为替换父类的行为。
方法的重写需要符合下面的三个要点:
1、“==”: 方法名、形参列表相同。
2、“≤”:返回值类型和声明异常类型,子类小于等于父类。
3、“≥”:访问权限,子类大于等于父类。
方法重写
public static void main(String[] args) { Vehicle v1 = new Vehicle(); Vehicle v2 = new Horse(); Vehicle v3 = new Plane(); v1.run(); v2.run(); v3.run(); v2.stop(); v3.stop(); } } class Vehicle { // 交通工具类 public void run() { System.out.println("跑...."); } public void stop() { System.out.println("停止不动"); } } class Horse extends Vehicle { // 马也是交通工具 public void run() { // 重写父类方法 System.out.println("四蹄翻飞,嘚嘚嘚..."); } } class Plane extends Vehicle { public void run() { // 重写父类方法 System.out.println("天上飞!"); } public void stop() { System.out.println("空中不能停,坠毁了!"); } }Object类基本特性
Object类是所有Java类的根基类,也就意味着所有的Java对象都拥有Object类的属性和方法。如果在类的声明中未使用extends关键字指明其父类,则默认继承Object类。
public class Person {/*extends Object*/ } //等价于: public class Person extends Object { }toString方法
Object类中定义有public String toString()方法,其返回值是 String 类型。在打印输出或者用字符串连接对象时,会自动调用该对象的toString()方法。
源码
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } 返回“类名+@+16禁止的hashcode”toString()方法测试和重写toString()方法
class User2 { String name; int age; //tostring方法重写Override public String toString() { return "info:"+name+",年龄:"+age; } } public class TestToString { public static void main(String[] args) { User2 u=new User2(); u.age=20; u.name="李东"; System.out.println(u);//info:李东,年龄:20 //参见TestObject //tostring方法 TestToString t = new TestToString(); System.out.println(t);//cn.test.oo.TestToString@1b6d3586 } }==和equals方法
“==”代表比较双方是否相同。
如果是基本类型则表示值相等,如果是引用类型则表示地址相等即是同一个对象。
Object类中定义的 public boolean equals(Object obj)方法,提供定义“对象内容相等”的逻辑。
Object 的 equals 方法默认就是比较两个对象的hashcode,是同一个对象的引用时返回 true 否则返回 false。
equals方法测试
public class TestEquals { public static void main(String[] args){ String s1 = new String("尚学堂"); String s2 = new String("尚学堂"); System.out.println(s1==s2); //false, 两个字符串不是同一个对象 System.out.println(s1.equals(s2)); //true, 两个字符串内容相同 User u1 = new User(123,"高崎","123456"); User u2 = new User(123,"高希希","123456"); System.out.println(u1==u2); //false,两个字符串不是同一个对象 System.out.println(u1.equals(u2)); //false,两个字符串内容不同 } } class User { int id; String name; String pwd; public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } }我们可以根据我们自己的要求重写equals方法。
自定义类重写equals方法
public class TestEquals { public static void main(String[] args){ String s1 = new String("尚学堂"); String s2 = new String("尚学堂"); System.out.println(s1==s2); //false, 两个字符串不是同一个对象 System.out.println(s1.equals(s2)); //true,两个字符串内容相同 User u1 = new User(123,"高崎","123456"); User u2 = new User(123,"高希希","123456"); System.out.println(u1==u2);//false,两个字符串不是同一个对象 System.out.println(u1.equals(u2));//true->id相同即返回true } } class User { int id; String name; String pwd; public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } //重写equals方法 public boolean equals(Object obj){ if(obj == null){ return false; } else{ if(obj instanceof User) { User c = (User)obj; if(c.id==this.id){ return true; } } } return false; } }super关键字
super是直接父类对象的引用。可以通过super来访问父类中被子类覆盖的方法或属性。
使用super调用普通方法,语句没有位置限制,可以在子类中随便调用。
若是构造方法的第一行代码没有显式的调用super(...)或者this(...),
那么Java默认都会调用super(),含义是调用父类的无参数构造方法。
这里的super()可以省略。
super关键字的使用
public class TestSuper01 { public static void main(String[] args) { new ChildClass().f(); } } class FatherClass { public int value; public void f(){ value = 100; System.out.println ("FatherClass.value="+value); } } class ChildClass extends FatherClass { public int value; public void f() { //调用父类对象的普通方法 //此super()即使省略也会默认调用 super.f();//FatherClass.value=100 value = 200; System.out.println("ChildClass.value="+value);//ChildClass.value=200 System.out.println(value);//200 //调用父类对象的成员变量 System.out.println(super.value); //200 } }继承树追溯
属性/方法查找顺序:(比如:查找变量h)
1、查找当前类中有没有属性h
2、依次上溯每个父类,查看每个父类中是否有h,直到Object
3、如果没找到,则出现编译错误
4、上面步骤,只要找到h变量,则这个过程终止
构造方法调用顺序:
构造方法第一句总是:super(…)来调用父类对应的构造方法。
流程是:先向上追溯到Object,然后再依次向下执行类的初始化块和构造方法,直到当前子类为止。
静态初始化块调用顺序,与构造方法调用顺序一样,不再重复。
构造方法向上追溯执行测试
public class TestSuper02 { public static void main(String[] args){ System.out.println("开始创建一个ChildClass......");//开始创建一个ChildClass...... new ChildClass2(); } } class FatherClass2{ public FatherClass2(){ System.out.println("创建FatherClass"); } } class ChildClass2 extends FatherClass2{ public ChildClass2(){ //super();//super可省略,但依然有作用,会先调用父类->//创建FatherClass System.out.println("创建ChildClass");//创建ChildClass } }编程中封装的具体优点:
1、提高代码的安全性。
2、提高代码的复用性。
3、高内聚:封装细节,便于修改内部代码,提高可维护性。
4、低耦合:简化外部调用,便于调用者使用,便于扩展和协作。
class Person { String name; int age; @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) { Person p = new Person(); p.name = "小红"; p.age = -45;//年龄可以通过这种方式随意赋值,没有任何限制 System.out.println(p); } } //如果没有使用封装的话,便可以给年龄赋值成任意的整数,这显然不符合我们的正常逻辑思维。封装的实现—使用访问控制符
Java是使用“访问控制符”来控制哪些细节需要封装,哪些细节需要暴露的。 Java中4种“访问控制符”分别为private、default、protected、public,它们说明了面向对象的封装性,所以我们要利用它们尽可能的让访问权限降到最低,从而提高安全性。
1、private表示私有,只有自己类能访问
2、default表示没有修饰符修饰,只有同一个包的类能访问
3、protected表示可以被同一个包的类以及其他包中的子类访问
4、public表示可以被该项目的所有包中的所有类访问
封装-JavaBean的规则
为写成JavaBean,类必须是具体的和公共的,并且具有无参数的构造器。
JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性,set和get方法获取。
JavaBean的优点是充分提高了代码的可重用性,并且对软件的可维护性和易维护性起到了积极作用。
多态指的是同一个方法调用,由于对象不同可能会有不同的行为。
多态的要点:
1、多态是方法的多态,不是属性的多态(多态与属性无关)。
2、多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。
3、父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
对象的转型(casting)
父类引用指向子类对象,我们称这个过程为向上转型,属于自动类型转换。
向上转型后的父类引用变量只能调用它编译类型的方法,不能调用它运行时类型的方法。
这时,我们就需要进行类型的强制转换,我们称之为向下转型。
public class TestCasting { public static void main(String[] args) { Object obj = new String("北京尚学堂"); // 向上可以自动转型 // obj.charAt(0) 无法调用。编译器认为obj是Object类型而不是String类型 /* 编写程序时,如果想调用运行时类型的方法,只能进行强制类型转换。 * 不然通不过编译器的检查。 */ String str = (String) obj; // 向下转型 System.out.println(str.charAt(0)); // 位于0索引位置的字符 System.out.println(obj == str); // true.他们俩运行时是同一个对象 } }在向下转型过程中,必须将引用变量转成真实的子类类型(运行时类型),
否则会出现类型转换异常ClassCastException。
public class TestCasting2 { public static void main(String[] args) { Object obj = new String("北京尚学堂"); //真实的子类类型是String,但是此处向下转型为StringBuffer StringBuffer str = (StringBuffer) obj; System.out.println(str.charAt(0)); } }为了避免出现这种异常,可以使用instanceof运算符进行判断。
public class TestCasting3 { public static void main(String[] args) { Object obj = new String("北京尚学堂"); if(obj instanceof String){ String str = (String)obj; System.out.println(str.charAt(0)); }else if(obj instanceof StringBuffer){ StringBuffer str = (StringBuffer) obj; System.out.println(str.charAt(0)); } } }final关键字
final关键字的作用:
1、修饰变量:被修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。
2、修饰方法:该方法不可被子类重写。但是可以被重载。
3、修饰类:修饰的类不能被继承。比如:Math、String等。
抽象方法
使用abstract修饰的方法,没有方法体,只有声明。
定义的是一种“规范”,就是告诉子类必须要给抽象方法提供具体的实现。
抽象类
包含抽象方法的类就是抽象类。
通过abstract方法定义规范,然后要求子类必须定义具体实现。
通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
作用:实现多态
定义抽象方法,才能使用子类的方法。
最简单的一个作用,实现模板。所有继承抽象类的子类,必须强制实现抽象类的抽象方法。
如果没有抽象方法,则子类可有可没有该方法。定义一个抽象方法后,所有子类就必须实现了。 另外,抽象类通常用于指向子类的实例: 比如: 抽象类 实例 = new 子类(); 实例.抽象方法();
抽象类的使用要点:
1、有抽象方法的类只能定义成抽象类。
2、抽象类不能实例化,即不能用new来实例化抽象类。
3、抽象类可以包含属性、方法、构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
4、抽象类只能用来被继承。
5、抽象方法必须被子类实现。
为什么需要接口?接口和抽象类的区别?
接口就是比“抽象类”还“抽象”的“抽象类”,可以更加规范的对子类进行约束。
全面地专业地实现了:规范和具体实现的分离。
抽象类还提供某些具体实现,接口不提供任何实现,接口中所有方法都是抽象方法。
接口和实现类不是父子关系,是实现规则的关系。
接口的本质
接口就是规范,定义的是一组规则,接口制定好后大家都要遵守。
面向对象的精髓,是对对象的抽象,最能体现这一点的就是接口。
区别:
1、普通类:具体实现
2、抽象类:具体实现,规范(抽象方法)
3、接口:规范
如何定义和使用接口
定义接口的详细说明
1、访问修饰符:只能是public或默认。
2、接口名:和类名采用相同命名机制。
3、extends:接口可以多继承。(类只能实现单继承)
4、常量:接口中的属性只能是常量,只能用public static final 修饰,可以不写。
5、方法:接口中的方法只能是:public abstract,省略的话,也是public abstract。
要点
1、子类通过implements来实现接口中的规范。
2、接口不能创建实例,但是可用于声明引用变量类型。
3、一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。
4、JDK1.7之前,接口中只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。
5、JDK1.8后,接口中包含普通的静态方法。
6、接口完全支持多继承。和类的继承类似,子接口扩展某个父接口,将会获得父接口中所定义的一切。
接口的使用
public class TestInterface { public static void main(String[] args) { Volant volant = new Angel(); volant.fly(); System.out.println(Volant.FLY_HIGHT); Honest honest = new GoodMan(); honest.helpOther(); } } /**飞行接口*/ interface Volant { int FLY_HIGHT = 100; // 总是:public static final类型的; void fly(); //总是:public abstract void fly(); } /**善良接口*/ interface Honest { void helpOther(); } /**Angle类实现飞行接口和善良接口*/ class Angel implements Volant, Honest{ public void fly() { System.out.println("我是天使,飞起来啦!"); } public void helpOther() { System.out.println("扶老奶奶过马路!"); } } class GoodMan implements Honest { public void helpOther() { System.out.println("扶老奶奶过马路!"); } } class BirdMan implements Volant { public void fly() { System.out.println("我是鸟人,正在飞!"); } } /* 结果: 我是天使,飞起来啦! 100 扶老奶奶过马路!一般情况,我们把类定义成独立的单元。
有些情况下,我们把一个类放在另一个类的内部定义,称为内部类(innerclasses)。
内部类可以使用public、default、protected 、private以及static修饰。
内部类只是一个编译时概念,一旦我们编译成功,就会成为完全不同的两个类。
对于一个名为Outer的外部类和其内部定义的名为Inner的内部类。
编译完成后会出现Outer.class和Outer$Inner.class两个类的字节码文件。
所以内部类是相对独立的一种存在,其成员变量/方法名可以和外部类的相同。
内部类的作用:
1、内部类提供了更好的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问。
2、内部类可以直接访问外部类的私有属性,内部类被当成其外部类的成员。 但外部类不能访问内部类的内部属性。
3、接口只是解决了多重继承的部分问题,而内部类使得多重继承的解决方案变得更加完整。
内部类的使用场合:
1、由于内部类提供了更好的封装特性,并且可以很方便的访问外部类的属性。所以,在只为外部类提供服务的情况下可以优先考虑使用内部类。
2、使用内部类间接实现多继承:每个内部类都能独立地继承一个类或者实现某些接口,所以无论外部类是否已经继承了某个类或者实现了某些接口,对于内部类没有任何影响。
内部类的分类
内部类主要分为成员内部类(非静态内部类、静态内部类)、匿名内部类、局部内部类。