static关键字
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
也就是说,通常变量的值如果想在不同对象之间共享值,就需要用static关键字修饰该类的变量(显式初始化赋值除外)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Chinese { String name;//实例变量,只有实例化之后才能使用,属于实例化对象的一部分,不能共用值(显式初始化赋值除外:String name = 1) static String country;//类变量不用实例化,直接类名.属性名就可以使用,是类的一部分,被所有这个类的实例化对象所共享,也称为静态变量 public static void main(String[] args) { Chinese c1 = new Chinese(); Chinese c2 = new Chinese(); Chinese c3 = new Chinese(); Chinese.country = "中国";//这里进行类变量赋值(静态变量显式赋值),下面的输出语句都可以访问到country的值 // Chinese.name = "xxx";//这里会报错,因为name没有static关键字修饰,类.变量名访问不到属性。 // c1.country = "中国"; // c2.country = "中国"; // c3.country = "中国"; //可以看见如果每个对象都要进行赋值一次,就会很麻烦 System.out.println(c1.country); System.out.println(c2.country); System.out.println(c3.country); } }
类属性、类方法的设计思想
类方法的使用和类变量一样:如果有些方法不想因为对象的不同而频繁通过new 对象方式去调用方法,方法就用static修饰,这样就可以不用创建对象,直接通过类名.方法名使用。补充:类变量和类方法也称为(静态变量和静态方法),我们常用的main方法就是静态方法(类方法)。通常静态方法的使用都是作工具类使用,例子如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Utils { //判断字符串是不是一个空字符串 public static boolean isEmpty(String arg) { boolean flag = false; //如果字符串是""空的,或者为null,返回true,表示为空 if(!(arg == "")&& !(arg == null)) { flag = true; } return flag; } public static void main(String[] args) { //可以直接使用静态方法,不用new创建对象,这样其他类使用工具类只需导包就可以使用,比较方便。 String str = ""; String str1 = null; String str2 = "xxx"; System.out.println(Utils.isEmpty(str));//false, System.out.println(Utils.isEmpty(str1));//false, System.out.println(Utils.isEmpty(str2));//true } }
注意:静态变量(类变量),这种可以被所有的实例化对象共享的属性,使用起来要谨慎,因为只要一改,所有类都能得到变化(如果该类使用了静态变量)。
还有就是静态方法也是一样,如果修改了静态方法,所有的类都会产生变化,静态方法最常用为工具类使用。
需要注意的是,静态变量/方法被权限修饰符修饰的权限,比如private修饰了该静态变量/方法,那么其他类就用不了该静态变量/方法。
还有静态方法里面不能用this/super关键字
this关键字指当前对象,super关键字指父类对象,但是static修饰的方法不需要创建对象就可以使用,所以使用了static修饰的方法,方法体里面如果使用了this/super关键字,编译期就会报错,因为this/super指的对象还没有创建就使用了。
单例设计模式
单例模式使用场景:
1、一般都是new对象太费劲,比如创建一个对象要执行1000多行代码需要花费10几秒时间。
2、频频的new新的对象没有必要
3、数据库连接池可以使用单例模式,初始化的时候创建譬如100个connection对象,然后再需要的时候提供一个,用过之后返回到pool中,我们用单例模式,是保证连接池有且只有一个。
4、网站在线人数统计;其实就是全局计数器,也就是说所有用户在相同的时刻获取到的在线人数数量都是一致的。要实现这个需求,计数器就要全局唯一,也就正好可以用单例模式来实现。
单例(Singleton)设计模式-饿汉式
饿汉式:先创建好对象,如果调用了,马上返回一个对象使用。
例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//一共两个类 //Single类 public class Single { private Single() {}//私有化构造函数,一般new对象都是调用构造函数 /** * 下面是饿汉式单例模式关键点 * 首先为什么使用static修饰创建的Single类,因为static静态变量是根据类加载而加载,而且静态变量只执行一次 * 所以代表这个new Single()对象只有一个 * 这里我发现,有两种方式可以实现饿汉式单例模式 * 第一种:就是public static Single single = new Single();把这个对象实例的访问权限改为public,然后就可以通过类名.属性直接访问该单例对象,当然public static Single getInstance()方法可以删除掉。 * 第二种:就是private static Single single = new Single();私有化这个对象实例变量。提供public修饰的方法public static Single getInstance(),通过类名.方法名访问。 * 这两种方式具体的区别我暂时不清楚,麻烦有大佬看到,解答一下 */ private static Single single = new Single(); //提供一个返回对象实例的方法,给其他类使用,这样其他类访问的都是同一个对象。 //注意返回对象实例这个方法是static修饰的,因为已经不能创建这个类的对象了,所以提供 类名.方法 这个入口给其他类访问 public static Single getInstance() { return single; } } //Test测试类 public class Test4 { public static void main(String[] args) { // Single s = new Single();//这里会报错,因为single类的构造方法访问不到,已经被私有化 Single s = Single.getInstance(); Single s1 = Single.getInstance(); System.out.println(s == s1);//true,返回的是同一对象 } }
单例(Singleton)设计模式-懒汉式
懒汉式:最开始,对象是null,直到有第一个人调用我,才new一个对象,之后所有调用我的都用这个对象。
补充:暂时懒汉式还存在线程安全问题,到多线程时,可修复。
例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//一共两个类 //Single1类 /** * * @author leak * 懒汉式单例模式 */ public class Single1 { //首先私有化构造方法,不让外部创建对象 private Single1() {} //static修饰是为了只存在一份,静态变量随着类加载而加载,且加载一次。 private static Single1 single = null; /** * * @return * 方法逻辑说明:首先single引用变量里面是null,当第一次调用getInstance()方法时,会进入if判断里面,当然第一次single里面的肯定为null * 所以第一次调用进入if里面后,创建一个Single1对象赋值给single引用变量,然后返回这个对象 * 然后其他类再次调用该getInstance()方法时,single里面肯定不为null,因为第一次调用已经把创建好的对象赋值给single变量了 所以single里面不为null,直接返回single对象,这个对象是第一次调用时的对象。 补充:因为single引用变量是静态变量,所有第一次创建对象赋值给这个single引用变量时,里面的值就已经是全局通用 */ public static Single1 getInstance() { if(single == null) { single = new Single1(); } return single; } } //Test测试类 public class Test4 { public static void main(String[] args) { // Single s = new Single();//这里会报错,因为single类的构造方法访问不到,已经被私有化 Single1 s = Single1.getInstance(); Single1 s1 = Single1.getInstance(); System.out.println(s == s1);//true,返回的是同一对象 } }
总结:单例模式,软件的运行有且仅有一个实例化对象,(只会new一次)
饿汉式和懒汉式的区别,就是什么时候new这个对象。
饿汉式,是在类加载之后,还没有人调用的时候,就先new好一个对象了,以后无论谁来调用getInstance()方法,都是直接返回之前new好的那个对象
懒汉式,是在第一次有人调用getInstance()方法时来new对象,以后再有人调用这个getInstance()方法直接就返回之前第一次new好的对象。
理解main方法的语法
用命令行执行编译后的class文件后,然后执行编译后的文件传参数:java Test 123 acds si23 。这里的Test类假设只有一个main方法在里面,main里面循环打印args数组里的值,就会输出 123 acds si23的值。
初始化块
普遍代码块
例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//Person类 public class Person { String name; public Person() { this.name = "张三"; System.out.println("执行的是构造方法"); } //非静态的代码块 { System.out.println("执行的是非静态代码块"); } { System.out.println("执行的是非静态代码块111111"); } } //Test类 public class Test4 { public static void main(String[] args) { new Person(); } }
Test类执行new Person()时,类里面的属性/方法执行顺序如下:
静态代码块
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//Person类 public class Person { String name; /** * 补充:静态代码块是初始化静态属性的,那为什么不一开始就进行初始化呢。 * 比如public static int age = 3; 一开始就进行初始化,那static代码块的作用在哪里呢 * 但是如果是初始化一个对象呢,这个对象有很多属性,方法需要初始化,那么就需要用到静态代码块进行初始化。 * 那有人肯定会说那也可以用构造器来初始化啊,如:static TestPerson tp = new TestPerson(参数1,参数2,参数3); * 注意:虽然可以用构造器进行初始化,但是也不能所有的属性/方法,都用构造器来进行初始化呀。 * 而且方法也不能通过构造器初始化,并且如果参数是集合、数组等引用类型的参数,构造器怎么进行赋值。 * 所以还是通过静态代码块对静态变量/方法进行初始化操作。 */ static TestPerson tp = new TestPerson();//TestPerson里面只有age/name属性,这里不提供代码了 static { tp.age = 12; tp.name = "zhutou"; } public static int age ; /** * 静态代码块 * 静态代码块里面只能初始化静态属性,或者使用静态变量/静态方法,不能使用非静态的变量/方法。 * 但是非静态代码块/方法可以调用静态的属性/方法。 * 静态代码块中为什么不能调用非静态变量/方法呢?因为静态变量/代码块初始化的顺序在非静态/方法的前面 * 在静态代码块里面进行初始化时,如果里面有非静态的变量/方法会报错,因为非静态变量/方法还没进行初始化就被调用了。 * 注意:静态代码块初始化完,才会去初始化其他。 * 补充,类加载里面的初始化顺序:静态变量初始化/静态代码块初始化->main方法->非静态变量初始化/非静态代码块初始化->构造方法 * 而且静态变量/代码块,随着类加载而加载,且只加载一次, */ static { // name = "";//会报错,这里静态代码块中,不能使用非静态的变量 // shows();//使用非静态方法报错 age = 11; System.out.println("====静态代码块和变量只执行一次====="); show();//静态方法 } //非静态方法 //非静态方法可以访问静态变量/方法 public void shows() { System.out.println("非静态方法"+age);//使用静态变量 show();//使用静态方法 } //静态方法 public static void show() { // System.out.println("name"+name);//报错,静态方法不能调用非静态变量 // shows();//报错,静态方法不能调用非静态方法 System.out.println("静态方法"); } public Person() { this.name = "张三"; System.out.println("执行的是构造方法"); } //非静态的代码块 { System.out.println("执行的是非静态代码块"); System.out.println("访问静态变量"+age); show();//调用静态方法 } { System.out.println("执行的是非静态代码块111111"); } } //Test类、测试静态属性/方法只执行一次/还有初始顺序 public class Test4 { public static void main(String[] args) { /** * 在程序 的运行过程中,非静态代码块/变量每次new对象都有重写执行,但静态代码块/变量只执行一次。 * 下面进行了两次new Person();但是静态变量/方法/代码块,只运行了一次。 */ new Person(); new Person(); } }
补充普通代码块的作用:对匿名内部类进行初始化。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//这里偷懒了用了上面的类,重点在于test方法,其他忽略 public class Person { public void test() { System.out.println("Person类的test方法"); System.out.println("Person类的属性name"+name); } public String name; /** * 补充:静态代码块是初始化静态属性的,那为什么不一开始就进行初始化呢。 * 比如public static int age = 3; 一开始就进行初始化,那static代码块的作用在哪里呢 * 但是如果是初始化一个对象呢,这个对象有很多属性,方法需要初始化,那么就需要用到静态代码块进行初始化。 * 那有人肯定会说那也可以用构造器来初始化啊,如:static TestPerson tp = new TestPerson(参数1,参数2,参数3); * 注意:虽然可以用构造器进行初始化,但是也不能所有的属性/方法,都用构造器来进行初始化呀。 * 而且方法也不能通过构造器初始化,并且如果参数是集合、数组等引用类型的参数,构造器怎么进行赋值。 * 所以还是通过静态代码块对静态变量/方法进行初始化操作。 */ static TestPerson tp = new TestPerson();//TestPerson里面只有age/name属性,这里不提供代码了 static { tp.age = 12; tp.name = "zhutou"; } public static int age ; /** * 静态代码块 * 静态代码块里面只能初始化静态属性,或者使用静态变量/静态方法,不能使用非静态的变量/方法。 * 但是非静态代码块/方法可以调用静态的属性/方法。 * 静态代码块中为什么不能调用非静态变量/方法呢?因为静态变量/代码块初始化的顺序在非静态/方法的前面 * 在静态代码块里面进行初始化时,如果里面有非静态的变量/方法会报错,因为非静态变量/方法还没进行初始化就被调用了。 * 注意:静态代码块初始化完,才会去初始化其他。 * 补充,类加载里面的初始化顺序:静态变量初始化/静态代码块初始化->main方法->非静态变量初始化/非静态代码块初始化->构造方法 * 而且静态变量/代码块,随着类加载而加载,且只加载一次, */ static { // name = "";//会报错,这里静态代码块中,不能使用非静态的变量 // shows();//使用非静态方法报错 age = 11; System.out.println("====静态代码块和变量只执行一次====="); show();//静态方法 } //非静态方法 //非静态方法可以访问静态变量/方法 public void shows() { System.out.println("非静态方法"+age);//使用静态变量 show();//使用静态方法 } //静态方法 public static void show() { // System.out.println("name"+name);//报错,静态方法不能调用非静态变量 // shows();//报错,静态方法不能调用非静态方法 System.out.println("静态方法"); } public Person() { this.name = "张三"; System.out.println("执行的是构造方法"); } //非静态的代码块 { System.out.println("执行的是非静态代码块"); System.out.println("访问静态变量"+age); show();//调用静态方法 } { System.out.println("执行的是非静态代码块111111"); } } //代码块{}初始化重点 public class Test4 { public static void main(String[] args) { /** * 匿名内部类 * 这种类没有类名,就不能显式的new的方法创建对象,如果还要在构造器中初始化属性就没有办法了,这样情况就要用代码块{}初始化 */ Person p = new Person() {//就这一个Person的匿名子类 //问题?现在把name改成李四,但是不能动Person类的代码。 注:Person构造器初始化name为:张三。 //用代码块代替构造器来进行初始化操作 { //这里回忆this和super的用法,当只写name="李四",代表的是当前对象的name属性,只是省略了this关键字 //this关键字会寻找当前对象的属性,如果没有则去父类寻找。 this.name = "李四";//所以这里可以是name=xxx,this.name=xxx,super.name=xxx } @Override public void test() { System.out.println("匿名子类重写父类方法,匿名子类没有类名不能显式 new 类名()这种方式创建对象,而且也不能通过构造器进行初始化"); } }; //输出结果在最后一行,前面的是之前静态的输出,可以忽略 System.out.println(p.name);//李四,由于上面代码块{}进行了初始化,把父类的属性重新赋值 p.test(); } }
补充:Java,除了老版本的东西以外,其他现存的这些东西,都是有作用的,而且是在某一个方向上有不可替代的作用。
final关键字
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class TestPerson { // final String name;//这里会报错,final修饰的变量必须要进行显式赋值,不能默认赋值 final static String NAME = "zs"; //而且被final修饰的变量,一般作常量使用,常量名一般全大写,也可以加上static修饰为静态常量。 static int age; int sex; public final void test() { System.out.println("被final修饰的方法,子类不能使用该方法"); } } final class son extends TestPerson{//这里的son类被final修饰过了,子类不能继承该类了。 // public void test() { // System.out.println("这里会报错,父类的test()方法被final修饰以后,子类不能使用了"); // } } //class son2 extends son{ //这里直接报错,因为son类已经被final修饰过了,不能被继承 //} class son3 extends TestPerson{ //这里复习一下静态,如果不在静态代码块里面使用age,是会报错的,因为age变量是static修饰的,所以只能在静态代码块里面进行操作 //当然在main方法里面也可以,因为main方法也是静态方法。 static { TestPerson.age = 10; } }
总结:final代表最终,可以修饰变量、方法、类。修饰变量,即为常量,只能显式赋值一次,不能改变;
修饰方法,子类不能使用父类的的方法;修饰类,类不能被继承。
抽象类
Animal里面的move方法不能给个固定的移动方法,因为不同的子类有不同的移动方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * @author leak * 抽象类,一般用abstract关键字修饰该类 * 只要类中有一个抽象方法,类就必须是一个抽象类。 * 子类继承抽象类必须重写所有的抽象方法 * 抽象子类可以不用重写父类的抽象方法 * 抽象类中可以有非抽象方法 */ abstract public class Animal { public abstract void test();//只要类中有一个抽象方法,类就必须是一个抽象类。 public abstract void move(); } //注意:子类继承抽象类必须重写所有的抽象方法,不然只要该类还存在抽象方法,那该类就必须是一个抽象类 class Dog extends Animal{ public void test() {} @Override public void move() { System.out.println("狗的移动方式是跑"); } } class Fish extends Animal{ public void test() {} @Override public void move() { System.out.println("鱼的移动方式是游"); } } abstract class Bird extends Animal{ //注意下面的声明了抽象方法,那么当前子类必须是抽象类,否则报错。 public abstract void move(); //当前Bird抽象子类,可以不用重写父类test()抽象方法 public void voice() { System.out.println("抽象类中可以存在不抽象的方法,不抽象的方法被子类继承,可以不用重写非抽象方法"); System.out.println("如果是抽象的方法,那么子类必须重写抽象方法,如果子类也是抽象类,那么可以不用重写抽象方法也行"); } } class birds extends Bird{//这里继承了抽象Bird类,可以不用重写非抽象的方法 //下面重写的方法都是抽象方法 public void move() {} public void test() {} }
思考
练习
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//抽象类 public abstract class Employee {//抽象员工类 int id; String name; double salary; public Employee() {} public abstract void work();//抽象方法 } class CommonEmployee extends Employee{ public CommonEmployee() {} public CommonEmployee(int id,String name,double salary) { this.id = id;//this会去找当前对象的id属性,如果找不到则去父类找 super.name = name; super.salary = salary; } @Override public String toString() { return "员工id:"+id+",员工name:"+name+",员工salary:"+salary; } @Override public void work() { System.out.println("普通员工"); } } class Manager extends Employee{ double bonus; public Manager() {} public Manager(int id,String name,double salary,double bonus) { this.id = id;//this会去找当前对象的id属性,如果找不到则去父类找 super.name = name; super.salary = salary; this.bonus = bonus; } @Override public String toString() { return "员工id:"+id+",员工name:"+name+",员工salary:"+salary+",员工奖金bonus:"+bonus; } @Override public void work() { System.out.println("领导"); } } //测试类 public class Test { public static void main(String[] args) { CommonEmployee coyy = new CommonEmployee(101,"猪头",2000); coyy.work(); System.out.println(coyy.toString()); Manager m = new Manager(1,"领导",5000,2000); m.work(); System.out.println(m.toString()); } }
模板方法设计模式
例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//模板类 /** * * @author leak * 模板设计模式 * 父类把确定的部分实现,不确定的部分用abstract修饰,给子类实现。 * 比如下面的例子,code方法用abstract修饰,是因为不知道code()方法执行的时间,不同的子类继承父类,重写code方法,执行的时间都不一样。 */ public abstract class Template { public abstract void code(); //获取code执行了多长时间 public final void getTime() { long start = System.currentTimeMillis(); code(); long end = System.currentTimeMillis(); System.out.println("code方法执行的时间:"+(end-start)); } } //子类继承父类,重写code方法 class TestTmp extends Template{ long k = 0; public void code() { for(int i = 0 ; i<10100000 ; i++) { k+=i; } System.out.println(k); } } //测试类 public static void main(String[] args){ Template t = new TestTmp(); t.getTime(); }
接口
例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//接口类一 public interface TestIn { /** * 接口类里面都是常量和抽象方法,不存在 变量和非抽象方法 * 就算定义了变量形式,但是隐式默认会给变量添加前缀 public static final 把变量转为常量 * 方法也是隐式默认在前面添加public abstract * 所以接口里面只存在抽象方法和常量 */ int ID = 1; //等同 public static final int ID = 1;//常量 void test();//等同 public abstract void test(); } //接口类二 public interface TestIn1 { void test1(); } //实现类 /** * * @author leak * 子类继承父类,只能继承一个父类,类可以实现多个接口,多个接口用 (,逗号) 分隔。 * 实现类可以实现多个接口类,java没有多继承,采用了多实现去代替多继承。 * 实现接口必须重写里面的方法 * 接口之间可以继承,接口继承接口。 * 实现类只能实现接口,不能实现抽象类,抽象类只能通过继承。 * 抽象类也可以实现接口,抽象类实现接口,可以不重写接口的方法,但是如果子类继承了(实现接口的抽象类),那么子类必须重写接口的方法。 */ public class TestInImpl implements TestIn,TestIn1{ //实现类必须实现接口的抽象方法 @Override public void test1() {} @Override public void test() {} }
复习:继承抽象类,子类必须重写所有抽象父类的抽象方法,否则该子类必须为抽象类。
注意:继承和实现的顺序,只能先继承后实现,不能把继承放到实现后面,会报错。
问题?
接口和抽象类很相似,好像接口能做的事用抽象类也能做,干嘛还要用接口呢?
概述:抽象类我们的知道,如果父类增加了抽象方法,那么依赖该父类的子类则需要全部改动。所以父类需要稳定的抽象,如果父类老是在该,基于这个父类的子类,子类的子类,这些子类都有受到影响,有时我们又确实需要给父类增加一些方法,那么不能直接在父类上下手,只能新建一个接口,在接口上扩展方法,其他需要的子类自行去实现该接口。(接口就类似第三方新增的功能,不会依赖于父类,独立存在,其他子类可以通过接口,去实现共同的方法)。
补充:接口和抽象类的区别
1.语法层面上的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.设计层面上的区别
1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将 飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly( ),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。
练习
结合上面小鸟的例子,这里Teacher子类继承父类Person(抽象类),但是又需要新增其他方法,但是不能直接给父类新增方法。
抽象类的使用:子类是不是该抽象类的一员。
接口的使用:子类有没有该接口的功能。
这里Cooking和Sing只是一个技能,不算一个抽象类,如果为了实现一个功能,而且把该功能作为一个抽象类,就很多余了,所以使用接口实现。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//抽象父类 public abstract class Person1 { int age; String name; int sex; abstract void showInfo(); } //技能接口1 public interface Cooking { void fry(); } //技能接口2 public interface Sing { void singing(); } //子类继承父类,实现两个技能接口 //子类继承抽象父类,实现两个技能接口 public class SCTeacher extends Person1 implements Cooking,Sing{ String course; public void setMess() { super.age=35; super.name="张三"; super.sex=1; this.course = "英语"; } @Override public void singing() { System.out.println(super.name+"老师擅长的唱法:美声"); } @Override public void fry() { System.out.println(super.name+"老师擅长的厨艺:炒菜"); } @Override void showInfo() { System.out.println("会唱歌的厨子是个老师的信息:"); System.out.println("年龄:"+super.age); System.out.println("姓名:"+super.name); System.out.println("性别:"+super.sex); System.out.println("科目:"+this.course); } } //测试类 public static void main(String[] args){ SCTeacher sct = new SCTeacher(); sct.setMess(); sct.showInfo(); sct.singing(); sct.fry(); }
接口细节补充:一个类可以实现多个无关接口,接口就像第三方技能,可以作为附加成分存在。
因为接口可以多实现,所以和继承关系相似,接口和实现类存在多态性。比如:
//格式:接口 引用变量 = new 接口实现类(); Cooking c = new SCTeacher(); c.fry(); Sing s = new SCTeacher(); s.singing();
接口的其他问题:如果实现接口的类中没有实现接口中的全部方法,必须将此类定义为抽象类。(和抽象父类必须重写抽象方法一样,必须重写所有抽象方法,否则该类必须为抽象类。)
接口也可以继承另一个接口,使用extends关键字。
总结:抽象类是对于一类事物的高度抽象,其中既有属性也有方法,抽象父类的继承是一个 "是不是"的关系;接口是对方法的抽象,也就是对一系列动作的抽象,接口的实现则是 "有没有"的关系。当需要对一类事物抽象的时候,应该使用抽象类,好形成一个父类;当需要对一系列的动作抽象,就使用接口,需要使用这些动作的类去实现相应的接口。
工厂方法
通常new对象这个形式创建对象,如果类名发生改变,那么new类名也跟着改变,很不方便,可以通过工厂方法模式把new对象隔离出来,那么如果具体的类名发生改变,只需要修改工厂里面的类名就行,不会影响到其他类里创建的类名。
例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//抽象产品 //产品接口,有一个抽象产品信息方法,打印产品信息 public interface Product { void productMess(); } //抽象工厂,只知道是生产产品,不用知道具体生产那种产品 //产品工厂,包含生产产品的抽象方法,由于生产抽象产品 public interface ProductFactory { Product produce(); } //具体的产品实现抽象产品 /** * @author leak * 具体的产品对象实现抽象产品,打印不同类型的产品信息 */ //书包产品 public class Bag implements Product{ @Override //打印书包的信息 public void productMess() { System.out.println("这个是小书包"); } } //飞机产品 class Plane implements Product{ @Override public void productMess() { System.out.println("这是大型飞机"); } } //钢笔产品 class Pen implements Product{ @Override public void productMess() { System.out.println("这是一支钢笔"); } } //具体的产品工厂,生产具体的产品 /** * * @author leak * 每个不同具体产品工厂实现抽象的产品工厂,产生具体的产品 */ //书包工厂,专生产书包 public class BagFactory implements ProductFactory{ @Override public Product produce() { System.out.println("生成书包"); return new Bag(); } } //飞机工厂,专生产飞机 class PlaneFactory implements ProductFactory{ @Override public Product produce() { System.out.println("生成飞机"); return new Plane(); } } //钢笔工厂,专生产钢笔 class PenFactory implements ProductFactory{ @Override public Product produce() { System.out.println("生成钢笔"); return new Pen(); } } //测试类 public static void main(String[] args){ /** * produce方法生产产品对象,productMess方法打印产品信息 * 根据不同类型产品工厂,调用produce和productMess方法,都是不同的方法,会生产不同的产品,打印不同的产品信息 */ //创建书包工厂 ProductFactory bagFactory = new BagFactory(); //生产书包,并且打印书包的信息 bagFactory.produce().productMess(); System.out.println("---------------------"); //创建飞机工厂 ProductFactory planeFactory = new PlaneFactory(); //生产飞机,并且打印飞机的信息 planeFactory.produce().productMess(); System.out.println("---------------------"); //创建飞机工厂 ProductFactory penFactory = new PenFactory(); //生产飞机,并且打印飞机的信息 penFactory.produce().productMess(); }
总结:通过工厂把new对象给隔离,通过产品的接口可以接受不同实际产品的实现类,实例的类名的改变不影响其他合作开发人员的编程。
工厂方法模式应用场景
- 当一个类不知道它所需要的对象的类时
在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可; - 当一个类希望通过其子类来指定创建对象时
在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。 - 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
内部类
内部类的特性
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * * @author leak * 内部类 */ public class Test2 { String name; String sex; int age; static int test; public static void main(String[] args) { Test2 a = new Test2();//创建Test2对象 a.setInfo();//setInfo方法是通过内部类进行赋值 a.showInfo(); } //下面是内部类详细介绍 class A{ private void setTest2Info() { //内部类使用外部类的属性格式:类名.this.属性 = xxx; Test2.this.name = "猪头"; Test2.this.sex = "男"; Test2.this.age = 42; } } //外部类想使用内部类的方法,需要创建内部类再使用,不能通过类名.方法调用 // 因为该方法不是静态方法,就算调用的方法使用static修饰,然后被调用的方法和类用static修饰,但是主类不能用static修饰 // 所以非静态的类不能调用静态的类里面的方法/变量 public void setInfo() { new A().setTest2Info(); } //内部类可以用final/private/protected/public/static 关键字修饰 final static class B{ int age; static int age2; } static class D{ void setInfo() { // Test2.this.name =" zs";这里会报错,因为本类是静态类,不可调用非静态类的属性/方法,而且主类(就是被public修饰的class)不可以用static修饰,内部其他类就可以。 B.age2= 1; // B.age = 1;//不可使用非静态属性 } } //内部抽象类可被继承 abstract class E{} class C extends E{ //继承抽象类 } //打印内部类设置好的属性 public void showInfo() { System.out.println(this.name); System.out.println(this.sex); System.out.println(this.age); } }
问题:内部类有什么用?上面的例子内部类能做的,外部类也可以,为什么要使用内部类呢,下面用例子说明内部类主要的作用。
内部类主要是为了解决Java不支持多继承的机制,因为Java不能继承多个类,比如:public class Test2 extends Person,Plane,Bag是不支持的
注意:多继承和多层继承是不同的概念。多继承是继承多个类,多层继承是子类继承父类,然后另外的子类再继承前面的子类,也就是 :父类<子类<子类。这样继承顶级父类只有一个。
例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class Test3 { public static void main(String[] args) { A a = new A(); a.testB();//A类已经通过内部类实现多继承,test方法已经重写了 a.testC(); } } /** * 问题:如果类A想同时获得类B和类C的方法,并且重写,怎么办 * 一般重写别的类的方法,都需要继承类,但是Java不支持多继承 * 也就是类A无法同时继承类B和类C,并且重写他们的方法 * 这时候就可以使用内部类来解决Java不支持多继承的问题 * 内部类实现多继承其实就是变相的实现了多继承,可以继承多个类 */ class A{ //创建方法调用继承的内部类重写的方法 public void testB() { new InnerB().testB(); } public void testC() { new InnerC().testC(); } //这里是class A的体内,下面的两个继承类属于内部类 //下面的两个内部类已经解决多继承的问题 private class InnerB extends B{ @Override public void testB() { System.out.println("已经继承B类,并且重写了B类的testB方法"); } } private class InnerC extends C{ @Override public void testC() { System.out.println("已经继承C类,并且重写了c类的testC方法"); } } } //下面的都是需要继承,然后重写方法,通过内部类实现多继承 class B{ public void testB() {} } class C{ public void testC() {} }
匿名内部类
例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
interface AA{ void fun1(); } public class Test4 { public void testIn() { System.out.println("Test4自带的方法"); } public static void main(String[] args) { Test4 t4 = new Test4() { //这里就是一个匿名内部类的体内 //这里面可以定义匿名内部类的属性/方法,然后用多态接收匿名内部类 t4现在接收的是匿名内部类 public void testIn() { System.out.println("匿名内部类的方法"); } }; //复习:testIn方法,匿名类里面虽然定义了,但是如果外部类没有testIn方法会报错,因为编译期调用的方法需要存在,才可调用 //这里表面是调用Test4的testIn方法,但是t4是指向匿名内部类,所以运行时,会调用匿名内部类的方法 //编译期和运行期,编译器调用的是Test4的testIn方法,运行时调用的匿名内部类的testIn方法 //所以如果要调用运行期的方法,那么编译期需要存在该方法 t4.testIn(); new Test4().callInner(new AA(){//new 外层类名().方法名(接口 形参){ //匿名内部类的体内 } //接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名 //变相通过new实现了接口,只不过是作为参数传递实现接口。 //重写了接口的方法 @Override public void fun1() { System.out.println("implement for fun1"); } });// 两步写成一步了 } //变相实现AA接口 public void callInner(AA a) { a.fun1(); } }
更多的匿名内部类例子,请点这里。
匿名类(也算是内部类)是一个子类,它一定会继承某个类。匿名类没有类名,它们不能被引用,不能有匿名类声明对象,只能在创建时用New语句和父类的构造方法创建一个对象。匿名类一定是内部类,匿名类一定可以访问外嵌类的成员变量和方法。