几篇比较好的博文 点击打开链接:类与继承 点击打开链接:接口与抽象类 点击打开链接:abstract与static 继承多态重写重载
1.关于final
表明这是一个常量,值不变。
成员变量若被声明称final,定义时必须被赋值,因为成员变量随着类的初始化而初始化,初始化时没有值的常量是要报错的
而方法内的局部变量被声明成final则没有这个问题,因为方法未必会在类初始化时调用,常量可以先声明,再赋值
不能被继承,不能被重写,只能赋值一次
2.关于static
static是静态的意思,表明被声明的变量或者方法在内存中只此一份,他就被大家共享了,不论谁改了他,别人在调用时这个值都已经变了
类的一个成员被static修饰后,该成员就属于类的成员了,被所有对象共享,如果成员是成员变量,那么该成员以及该成员的值就被所有对象所共享;如果成员是成员方法,那么该方法就被所有对象共享;相反如果成员没有用static修饰,那么每个对象就有自己的成员,这时可以说,每个对象的成员是他私有的,注意这和权限控制里的私有权限是两码事。可以想象,共享的方法只能访问共享的成员,是无法访问对象私有的成员的,而对象私有的成员方法是可以访问共享的成员的。打个比方,就像我们给公家干活肯定花的是公家的钱,绝不会花私人的钱,而给自己干活时,却可以花公家的钱,只要公家不追究。这也就是为什么static修饰的成员只能访问static修饰的成员,如static修饰的成员方法,static修饰的初始化块还有static修饰的内部类均如此。
在实际开发中,之所以给类定义成员变量,就是因为该类有不同状态的对象,比如我们定义人这个类,一般会定义年龄、姓名等属性,然后不同的人(这里就是人这个类的对象了)虽然都有年龄和姓名,但各有各的年龄,各有各的姓名,这就是上面说的每个对象的成员是该对象私有的。如果把年龄和姓名定义成static的,那就导致所有的人都有相同的年龄和姓名,显然这没有意义。
通常,我们在定义一个工具类时,一般把它的方法定义成static的,因为这种类只用作工具,只关注他的行为,不关注他的状态,所以不需要定义成员变量。使用这种工具类的方法时无需创建对象,既简单又节省资源。创建对象来调用反而麻烦且浪费资源,所以这种类被设计出来后就干脆不允许创建对象,因为其构造方法被设计成private权限了。比如我们用的Math和Arrays,还有Collections。这三个类时我们java中最常见的三个工具类。
静态的可以直接用类名去调用,而无需先实例化对象
静态方法可以被继承,但是当子类里有同名的时候,不论是静态还是非静态,父类的静态方法都是被隐藏,而不是被重写或者覆盖,想要访问需要有对象才可以
我们在用final定义常量时,习惯于再加上一个static,这就是出于节省内存的考虑,静态,大家共享。
程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问。
当new 一个对象的时候,并不是先在堆中为对象开辟内存空间,而是先将类中的静态方法(带有static修饰的静态函数)的代码加载到一个叫做方法区的地方,然后再在堆内存中创建对象。所以说静态方法会随着类的加载而被加载。当你new一个对象时,该对象存在于堆内存中,this关键字一般指该对象,但是如果没有new对象,而是通过类名调用该类的静态方法也可以
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存(故而会先看有没有静态的,有就先加载,没有就看别的),可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
在一个类的静态成员中去访问其非静态成员之所以会出错是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
而类又是在什么时候加载的呢?
由引导类加载器负责加载的核心类比如 String 类在 JVM 启动时(main 方法开始执行前)就会被加载,其它类在使用前(new 它对象或调用其静态方法访问静态域等等前)会被动态加载,要注意的是子类被加载前它的所有超类要根据由父到子的顺序被逐一加载
3.封装
类的存在就是封装的一种表现,用类来封装相关的数据和 方法,保证了数据的安全和系统的严密性。
4.继承
简单说 子 is a 父
父类有的,子类的都应该有
父类有的抽象方法(抽象就意味着必须实现),子类都必须实现,一般的可以看需求而定;另外子类可以选择重写,或者说覆盖,也就是说对父类的函数进行重新定义
有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写,overriding
子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。
如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
注:关于初始化,一个类肯定是必须要有构造函数来完成初始化的。
5.多态
对于非静态方法:编译看左边,运行看右边,因为存在重写的情况,所以运行时要看右边,看子类重写的方法
此时不能使用子类特有功能
要么 创建子类对象再调用特有方法;要么把父类引用强转为子类引用
在实际应用中,就是父类根据不同的子类调用不同的子类的方法,扩展性强~
6.接口
7.内部类 & 匿名对象
8.抽象类
比如,动物是抽象类,猫狗是具体对象,抽象类里不能有猫狗特有的属性,故我们不应该在动物类给出具体的实现,而是应该给出一个行为的声明即可
public class AnimalDemo {
public static void main(String[] arg){
Cat cat1 = new Cat();
cat1.eat();
cat1.sleep();
Cat cat2 = new Cat();
cat2.eat();
cat2.sleep();
Dog dog1 = new Dog();
dog1.eat();
dog1.sleep();
}
}
//父类,能吃能睡
class Animal{
String name = "animal";
public void eat(){
System.out.println("Animal.eat()");
}
public void sleep(){
System.out.println("Animal.sleep()");
}
}
//子类猫,继承了吃睡
class Cat extends Animal{
public void eat(){
System.out.println("cat.eat fish");
}
public void sleep(){
System.out.println("cat.sleep()");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog.eat meat");
}
}
此处,子类dog里没有实现sleep方法,但是却调用了这个方法,也可以,但实际调用的是父类的sleep方法
对于每个实例猫,都有同样的操作,可以抽出来,好比有两个饲养员,一个养猫,一个养狗
public class AnimalDemo {
public static void main(String[] arg){
Cat cat1 = new Cat();
keepcat(cat1);
Cat cat2 = new Cat();
keepcat(cat2);
Dog dog1 = new Dog();
keepDog(dog1);
}
public static void keepcat(Cat c){
c.eat();
c.sleep();
}
public static void keepDog(Dog d){
d.eat();
}
}
//父类,能吃能睡
class Animal{
String name = "animal";
public void eat(){
System.out.println("Animal.eat()");
}
public void sleep(){
System.out.println("Animal.sleep()");
}
}
//子类猫,继承了吃睡
class Cat extends Animal{
public void eat(){
System.out.println("cat.eat fish");
}
public void sleep(){
System.out.println("cat.sleep()");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog.eat meat");
}
}
再加一个子类bear,甚至更多,也可以类似操作
但是这里可以更方便的操作,来一个饲养员,只要给我一个动物,我就能养,不论这是什么动物
也就说 父类的引用指向了子类的对象
public class AnimalDemo {
public static void main(String[] arg){
Cat cat1 = new Cat();
keepanimal(cat1);
Cat cat2 = new Cat();
keepanimal(cat2);
Dog dog1 = new Dog();
keepanimal(dog1);
Bear bear1 = new Bear();
keepanimal(bear1);
//多态,父类引用指向了子类的对象,调用了子类的方法
Animal animal1 = new Cat();
keepanimal(animal1);
//匿名内部类
keepanimal(new Bear());
}
public static void keepanimal(Animal a){
a.eat();
a.sleep();
}
}
//父类,能吃能睡
class Animal{
String name = "animal";
public void eat(){
System.out.println("Animal.eat()");
}
public void sleep(){
System.out.println("Animal.sleep()");
}
}
//子类猫,继承了吃睡
class Cat extends Animal{
public void eat(){
System.out.println("cat.eat fish");
}
public void sleep(){
System.out.println("cat.sleep()");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog.eat meat");
}
public void sleep(){
System.out.println("dog.sleep()");
}
}
class Bear extends Animal{
public void eat(){
System.out.println("Bear.eat honey");
}
public void sleep(){
System.out.println("Bear.sleep()");
}
}
倘若此时 子类dog有一个特有的看家的方法,keephouse(),这个时候不能直接在keepAnimal里调用这个方法,饲养员只能有通用的技能,狗狗太另类
怎搞
要么强转成子类,要么创建子类对象
创建子类对象
//创建子类对象,可以直接调用子类的方法,也可以通过饲养员调用
Dog animal3 = new Dog();
/*animal2.eat();
animal2.sleep();*/
keepanimal(animal2);
animal3.keepHouse();
强转
Animal animal2 = new Dog();
/*animal2.eat();
animal2.sleep();*/
keepanimal(animal2);
/*//多态里无法直接引用子类特有方法,可强转
animal2.keepHouse();*/
((Dog)animal2).keepHouse();
public class AnimalDemo {
public static void main(String[] arg){
Cat cat1 = new Cat();
keepanimal(cat1);
Cat cat2 = new Cat();
keepanimal(cat2);
Dog dog1 = new Dog();
keepanimal(dog1);
Bear bear1 = new Bear();
keepanimal(bear1);
//多态,父类引用指向了子类的对象,调用了子类的方法
Animal animal1 = new Cat();
keepanimal(animal1);
//匿名内部类
keepanimal(new Bear());
Animal animal2 = new Dog();
/*animal2.eat();
animal2.sleep();*/
keepanimal(animal2);
/*//多态里无法直接引用子类特有方法,可强转
animal2.keepHouse();*/
((Dog)animal2).keepHouse();
//创建子类对象,可以直接调用子类的方法,也可以通过饲养员调用
Dog animal3 = new Dog();
/*animal2.eat();
animal2.sleep();*/
keepanimal(animal2);
animal3.keepHouse();
}
public static void keepanimal(Animal a){
a.eat();
a.sleep();
}
}
//父类,能吃能睡
class Animal{
String name = "animal";
public void eat(){
System.out.println("Animal.eat()");
}
public void sleep(){
System.out.println("Animal.sleep()");
}
}
//子类猫,继承了吃睡
class Cat extends Animal{
public void eat(){
System.out.println("cat.eat fish");
}
public void sleep(){
System.out.println("cat.sleep()");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog.eat meat");
}
public void sleep(){
System.out.println("dog.sleep()");
}
public void keepHouse(){
System.out.println("dog.keep house()!");
}
}
class Bear extends Animal{
public void eat(){
System.out.println("Bear.eat honey");
}
public void sleep(){
System.out.println("Bear.sleep()");
}
}
我们可以把JAVA中的类分为以下三种:
类:使用class定义且不含有抽象方法的类。
抽象类:使用abstract class定义的类,它可以含有,也可以不含有抽象方法。
接口:使用interface定义的类。
在这三种类型之间存在下面的继承规律:
类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。
抽象类可以继承(extends)类,可以继承(extends)抽象类,可以继承(implements)接口。
接口只能继承(extends)接口。
请注意上面三条规律中每种继承情况下使用的不同的关键字extends和implements,它们是不可以随意替换的。大家知道,一个普通类继承一个接口后,必须实现这个接口中定义的所有方法,否则就只能被定义为抽象类。我在这里之所以没有对implements关键字使用“实现”这种说法是因为从概念上来说它也是表示一种继承关系,而且对于抽象类implements接口的情况下,它并不是一定要实现这个接口定义的任何方法,因此使用继承的说法更为合理一些。
以上三条规律同时遵守下面这些约束:
类和抽象类都只能最多继承一个类,或者最多继承一个抽象类,并且这两种情况是互斥的,也就是说它们要么继承一个类,要么继承一个抽象类。
类、抽象类和接口在继承接口时,不受数量的约束,理论上可以继承无限多个接口。当然,对于类来说,它必须实现它所继承的所有接口中定义的全部方法。
抽象类继承抽象类,或者实现接口时,可以部分、全部或者完全不实现父类抽象类的抽象(abstract)方法,或者父类接口中定义的接口。
类继承抽象类,或者实现接口时,必须全部实现父类抽象类的全部抽象(abstract)方法,或者父类接口中定义的全部接口。
重写,也就是覆盖,英文名是overriding,是指在继承情况下,子类中定义了与其基类中方法具有相同型构的新方法,就叫做子类把基类的方法重写了。这是实现多态必须的步骤。比如对接口方法的实现,接口里的方法一般只有方法名,没有方法体,需要子类自己去重写实现
重载,英文名是overloading,是指在同一个类中定义了一个以上具有相同名称,但是型构不同的方法。在同一个类中,是不允许定义多于一个的具有相同型构的方法的