面向对象
什么是面向对象
封装 普通内部类 定义在类中的类,可以使用外部类所有属性和方法。普通内部类属于具体对象,因此不能声明 static 成员变量和方法。
成员内部类依附外部类而存在。也就是说,如果要创建普通内部类的对象,就必须首先存在外部类的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test { public static void main (String[] args) { Outter outter = new Outter (); Outter.Inner inner = outter.new Inner (); inner.output(); } } class Outter { private int num = "10" ; class Inner { void output () { System.out.println(num); } } }
局部内部类 定义在一个方法或者一个作用域里的内部类。对局部内部类的访问仅限于方法内或者该作用域内,且局部内部类不能被访问权限所修饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test { public static void main (String[] args) { Factory f = new Factory (); Gun myrifle = f.getRifle(); } } class Factory { public Gun getRifle () { class Rifle extends Gun { int len = 60 ; } return new Rifle (); } }
匿名内部类 匿名内部类不用定义名称,但必须继承一个父类或实现一个接口。由于没有类名,匿名内部类不能定义构造器。在创建匿名内部类的时候会立即创建它的实例。因此匿名内部类只能使用一次,通常用来简化代码编写。
最常用的情况就是在多线程的实现上,创建线程类传入参数需要继承 Thread 类或实现 Runnable 接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface Person { public void eat () ; } public class Demo { public static void main (String[] args) { Person p = new Person () { public void eat () { System.out.println("eat apple" ); } }; p.eat(); } }
JDK 1.8 中引入了 Lambda 表达式,你甚至连方法名都不需要写。
1 2 3 4 5 6 7 8 public class Demo { public static void main (String[] args) { Person p = new Person (() -> { System.out.println("eat apple" ); }); p.eat(); } }
局部内部类和匿名内部类都定义在方法中,如果调用方法中的其他局部变量,只能调用外部类的局部 final 变量。因为在多线程中,外部类方法中定义的变量 A 在方法执行完毕后生命周期就结束了,而此时 Thread 对象的生命周期很可能还没有结束。内部类方法中访问的变量 A 实际上是拷贝。这就必须限定变量为 final,否则改动将导致数据不一致。
1 2 3 4 5 6 7 8 9 10 11 public class Test { public void test (final int b) { final int a = 10 ; new Thread (){ public void run () { System.out.println(a); System.out.println(b); }; }.start(); } }
静态内部类 静态内部类是不需要依赖于外部类,可以在不创建外部类对象的情况下创建内部类的对象。静态内部类不能使用外部类的非 static 成员变量或者方法。
1 2 3 4 5 6 7 8 9 10 11 12 public class Test { public static void main (String[] args) { Outter.Inner inner = new Outter .Inner(); } } class Outter { static class Inner { int data = 0 ; } }
继承 类的继承 子类继承父类后,无需定义也可使用父类定义好的 public/protected 方法和属性。也可以进行扩展和方法的重写。
父类的属性值 不会被子类继承,但子类可以通过父类提供的方法得到父类的属性值。
父类的 static 方法 不会被子类继承,子类的 static 方法会隐藏父类的同名 static 方法。
父类的构造方法 不会被子类继承,子类必须在构造方法首行调用父类构造方法(先构造父类,再构造子类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 final public class Trunk extends Car { String brand; String description = "this is a trunk" ; int goods; public void load (int num) { this .goods += num; } public Trunk (String brand) { super (brand); this .goods = 0 ; } @Override public void go (String loc) { super .go(loc); System.out.print(" with" + goods + "goods" ); this .goods = 0 ; } }
Object 类是一切 java 类的父类。对于普通的 java 类,即便不声明也默认继承了 Object 类。
接口继承 和类的继承类似。但 Java 类只能单继承,而 Java 接口可以多继承。
1 2 3 interface Charge extends Move , Fight{ public abstract void kill (int num) ; }
多态 继承多态
重载 (overload):定义多种同名方法,调用时根据传入参数判定调用哪种方法。
重写 (override):子类定义完全相同的方法覆盖父类。
重写是多态的前提,其允许父类引用指向子类对象(引用类型为父类,指向的实际对象类型为子类)。
Car mycar = new Trunk("Benz");
但不允许子类引用指向父类对象。
Trunk mycar = new Car("Benz");
如果两个类之间存在继承关系,可以进行强制类型转换。强制类型转换只能改变引用类型,实际指向对象类型不会发生变化。
Trunk newCar = (Trunk)mycar;
方法多态
调用普通方法
子类同名方法会覆盖父类。执行方法根据实际对象类型来判定,即执行子类重写的方法。
调用 static / private / final 以及构造方法
特殊方法不能被覆盖,不存在多态。执行方法会根据引用类型来判定,即执行父类方法。
调用成员变量
父类属性值不会被子类继承,不存在多态。调用变量会根据引用类型来判定,即得到父类属性值。
1 2 3 4 5 6 7 8 Car myCar = new Trunk ("Benz" );myCar.go("London" ); myCar.showNum(); System.out.print(myCar.description); Trunk newCar = (Trunk)mycar; System.out.print(newCar.description);
反射机制 JAVA 是动态编译语言(运行时才确定类型),支持反射机制。在运行状态中
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性。
通过反射机制能更好地支持多态,降低模块耦合,提高代码灵活度(根据传入类名不同,就能实例化出不同的对象)。
但是在性能上会有较大的损耗。
尽管在应用层面很少使用反射机制,但在设计基础框架的时候反射机制非常有用。
反射机制运用 类的相关信息保存在以下类中,通过特定方法获取其对象能够知道这个类的信息。
Class 类 :类
Constructor 类 :类的构造方法
Field 类 :类的属性
Method 类 :类的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class Reflection { public static void main (String[] args) { Student stu = new Student (); Class stuClass = stu.getClass(); Class stuClass = Student.class; Class stuClass = Class.forName("Reflection.Student" ); String name = stuClass.getName()); Constructor[] conArray = stuClass.getConstructors(); Constructor[] conArray = stuClass.getDeclaredConstructors(); Constructor con = stuClass.getConstructor(null ); Constructor con = stuClass.getDeclaredConstructor(char .class); Field[] fieldArray = stuClass.getFields(); Field[] fieldArray = stuClass.getDeclaredFields(); Field f = stuClass.getField("name" ); Method[] methodArray = stuClass.getMethods(); Method[] methodArray = stuClass.getDeclaredMethods(); Method m = stuClass.getMethod("main" , String.class); Object obj = con.newInstance(); f.set(obj, "X-man" ); m.invoke(obj, "X-man" ); }
泛型 在编译时检查类型安全,编译过后泛型被擦除、实际类型才确定。反射是在编译期模拟 java 运行时的环境读取和调用程序,因此不能获得泛型的实际类型。但可以通过反射越过泛型检查:
在 String 泛型的集合中,你甚至可以添加一个 Integer 类型的值。
1 2 3 4 5 6 7 8 public class Demo { public static void main (String[] args) throws Exception{ ArrayList<String> strList = new ArrayList <>(); Class listClass = strList.getClass(); Method m = listClass.getMethod("add" , Object.class); m.invoke(strList, 100 ); } }