35.面向对象

来源:互联网 发布:暖气管道钥匙 淘宝 编辑:程序博客网 时间:2024/05/22 14:38

1.面向过程和面向对象的区别

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。 
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。


2.this(两种用法,回顾4 )


3.supper

(1)调用父类构造函数

(2)直接访问父类中的成员

package test;public class test1 {public static void main(String[] args) {Son gyc = new Son();gyc.look();}}class Dad {Dad(){};public int age = 45; public void play() {System.out.print("Dad is playing");}}class Son extends Dad{Son(){super();//子类构造函数 调用父类的构造函数;注意:super必须是在子类中执行的第一个语句}public void play() {System.out.println("son is playing");}public void look() {play();super.play();//调用父类方法super.age = 46; //修改父类中的ageSystem.out.println(super.age);//访问父类中的变量}}


4.成员变量和局部变量
(1)根据定义变量位置的不同,可以将变量分为成员变量和局部变量
       成员变量是在类范围内定义的变量
       局部变量是在一个方法内定义的变量

(2)成员变量可以分为:
       实例属性 (不用static修饰)
              随着实例属性的存在而存在
       类属性 (static修饰)
              随着类的存在而存在

(3)局部变量可分为:
       形参(形式参数)
              在整个方法内有效
       方法局部变量 (方法内定义)
              从定义这个变量开始到方法结束这一段时间内有效
       代码块局部变量 (代码块内定义)
              从定义这个变量开始到代码块结束这一段时间内有效

(4)成员变量无需显式初始化,系统会自动对其进行默认初始化

(5)局部变量除了形参外,都必须显示初始化,也就是要指定一个初始值,否则不能访问。

(6)java允许局部变量和成员变量重名,局部变量会覆盖成员变量的值

package test;public class test1 {public static void main(String[] args) {int age =20;test1 t = new test1();t.person();}public void person() {int age =10;System.out.println(age);//局部变量会覆盖成员变量的值}}

5.封装

(1)概念:首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与 外界联系

(2)为什么需要封装?

封装符合面向对象设计原则的第一条:单一性原则,一个类把自己该做的事情封装起来,而不是暴露给其他类去处理,当内部的逻辑发生变化时,外部调用不用因此而修改,他们只调用开放的接口,而不用去关心内部的实现(就像主机机箱,你不需要知道内部构造,只要会按电源就行,如果把没壳的机箱给你,你可能瞎鼓捣坏)

package test;public class test1 {public static void main(String[] args) throws Exception {test1 t = new test1();t.setName("成子");t.getName();t.setAge(160);t.getAge();}private String name;private int age;public void setName(String name) {this.name = name;}public void setAge(int age)throws Exception {if(age>150) {//封装age的检验逻辑,而不是暴露给每个调用者去处理throw new Exception("太大了吧");}this.age = age;}public void getName() {System.out.println(name);}public void getAge() {System.out.println(age);}}


6.继承

为什么要继承?
代码重用是一点,最重要的还是所谓向上转型,即父类的引用变量可以指向子类对象,这是Java面向对象最重要特性多态的基础

Java的类可以分为三类:
(1)类:使用class定义,没有抽象方法
(2)抽象类:使用abstract class定义,可以有也可以没有抽象方法
(3)接口:使用inerface定义,只能有抽象方法


在这三个类型之间存在如下关系:
(1)类可以extends:类、抽象类(必须实现所有抽象方法),但只能extends一个,可以implements多个接口(必须实现所有接口方法)
(2)抽象类可以extends:类,抽象类(可全部、部分、或者完全不实现父类抽象方法),可以implements多个接口(可全部、部分、或者完全不实现接口方法)
(3)接口只能extends一个接口


继承以后子类可以得到什么:
(1)子类拥有父类非private的属性和方法
(2)子类可以添加自己的方法和属性,即对父类进行扩展
(3)子类可以重新定义父类的方法,即多态里面的覆盖,后面会详述


关于构造函数:
(1)有参数构造函数不能被继承,子类必须通过super()显示调用父类的构造函数
(2)创建子类时,编译器会自动调用父类的 无参构造函数

package test;public class test1 {public static void main(String[] args)  {Son s = new Son();s.look();}}class Father {public Father(int a ) {System.out.println(a);}public Father() {};public void play() {System.out.println("father is playing");}}class Son extends Father{public Son() {} //因为第二点,所以不用写成public Son() {super();}public Son(int a) {super(a);//有参数,必须显示的调用}public void look() {super.play();}}

7.多态

(1)在了解多态之前,首先需要知道方法的唯一性标识即什么是相同/不同的方法:
一个方法可以由:修饰符如public、static+返回值+方法名+参数+throw的异常 5部分构成
其中只有方法名和参数是唯一性标识,意即只要方法名和参数相同那他们就是相同的方法
所谓参数相同,是指参数的个数,类型,顺序一致,其中任何一项不同都是不同的方法

(2)何谓重载:
重载是指一个类里面(包括父类的方法)存在方法名相同,但是参数不一样的方法,参数不一样可以是不同的参数个数、类型或顺序
如果仅仅是修饰符、返回值、throw的异常 不同,那这是2个相同的方法,编译都通不过,更不要说重载了
(3)何谓覆盖/重写:
覆盖描述存在继承关系时子类的一种行为
子类中存在和父类相同的方法即为覆盖,何谓相同方法请牢记前面的描述,方法名和参数相同,包括参数个数、类型、顺序

(4)覆盖/重写的规则:
子类不能覆盖父类private的方法,private对子类不可见,如果子类定义了一个和父类private方法相同的方法,实为新增方法
重写方法的修饰符一定要大于被重写方法的修饰符(public > protected > default > private)
重写抛出的异常需与父类相同或是父类异常的子类,或者重写方法干脆不写throws
重写方法的返回值必须与被重写方法一致,否则编译报错
静态方法不能被重写为非静态方法,否则编译出错


(5)理解了上述知识点,是时候定义多态了:

Java实现多态有三个必要条件:继承、重写、向上转型。
多态可以说是“一个接口,多种实现”或者说是父类的引用变量可以指向子类的实例,被引用对象的类型决定调用谁的方法,但这个方法必须在父类中定义
多态可以分为两种类型:编译时多态(方法的重载)和运行时多态(继承时方法的重写),编译时多态很好理解,后述内容针对运行时多态
运行时多态依赖于继承、重写和向上转型

package test;public class test1 {public static void main(String[] args)  {Father s = new Son();s.play();}}class Father {public void play() {System.out.println("father is playing");look();}public void look() {System.out.println("father is looking");}}class Son extends Father{public void play(String a) { //重载System.out.println("Son is playing");look();}public void look() { //重写System.out.println("Son is looking");}}
结果:father is playing
Son is looking


由运行结果可以看出,Son的play方法有参数,但调用S.play()未给参数,所以向上转型调用了father的play方法
    而Son重写了look方法,所以仍是用的son的play


8.初始化块

在Java中,有两种初始化块:静态初始化块和非静态初始化块.
静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员.
非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量.

package test;public class test1 {public static void main(String[] args)  {Kuai k1 = new Kuai();Kuai k2 = new Kuai();Kuai k3 = new Kuai();Kuai k4 = new Kuai();}}class Kuai{{System.out.println("欢迎");}static {System.out.println("你好!");}}

结果:

你好!
欢迎
欢迎
欢迎
欢迎


9.包装类

(1)Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class),有些地方也翻译为外覆类或数据类型类。
         包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示:

包装类对应表

基本数据类型

包装类

byte

Byte

boolean

Boolean

short

Short

char

Character

int

Integer

long

Long

float

Float

double

Double

   对于包装类说,这些类的用途主要包含两种:
                   a、作为和基本数据类型对应的类类型存在,方便涉及到对象的操作。
                   b、包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法。

package test;public class test1 {public static void main(String[] args)  {Integer i2 = new Integer(100);//将int类型转化为Integer类型System.out.println(i2);Integer i3 = 10;//JDK 1.5后,引入自动装箱/拆箱,int类型会自动转换为Integer类型//基本数据类型和对应的包装类转换时,系统将自动进行,这将大大方便程序员的代码书写。String s1 = "123";int i4= Integer.parseInt(s1);//parseInt方法将String类型转化为Intint i5 = 456;String s2 = Integer.toString(i5);//toString方法将Int转化为String}}

10.final

(1)final关键字的含义?
final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。

(2)final变量:凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量。final变量经常和static关键字一起使用,作为常量。

(3)final方法:final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。

(4)final类:使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String, Interger以及其他包装类。


(5)使用final关键字的好处
final关键字提高了性能。JVM和Java应用都会缓存final变量。
final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
使用final关键字,JVM会对方法、变量及类进行优化。

(6)关于final的重要知识点
1.final关键字可以用于成员变量、本地变量、方法以及类。
2.final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
3.你不能够对final变量再次赋值。
4.本地变量必须在声明时赋值。
5.在匿名类中所有变量都必须是final变量。
6.final方法不能被重写。
7.final类不能被继承。
8.final关键字不同于finally关键字,后者用于异常处理。
9.final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
10.接口中声明的所有变量本身是final的。
11.final和abstract这两个关键字是反相关的,final类就不可能是abstract的。(抽象类需要被继承才能使用,而被final修饰的类无法被继承,所以abstract和final是不能共存的。)
12.final方法在编译阶段绑定,称为静态绑定(static binding)。
13.没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
14.将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
15.按照Java代码惯例,final变量就是常量,而且通常常量名要大写

16.对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。


11.抽象类

(1)拥有抽象方法的类就是抽象类,抽象类要使用abstract关键字声明。

(2)抽象方法,是指没有方法体的方法,同时抽象方法还必须使用关键字abstract做修饰。

(3)抽象类的使用原则如下: 
1.抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public; 
2.抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理; 
3.抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类; 
4.子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。)

package test;public class test1 {public static void main(String[] args)  {Ab a = new Son();//向上转型a.birds();//被Son覆盖的方法}}abstract class Ab{abstract void birds();//抽象方法默认为public public void fly() {System.out.println("鸟在飞!");}}class Son extends Ab{public void birds() { //覆盖Ab中的birds方法System.out.println("我是小鸟");}}

12.接口

(1)接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,它是对行为的抽象

(2)接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误)即接口中的成员变量为常量(大写,单词之间用"_"分隔)

(3)而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。


package test;public class test1 {public static void main(String[] args)  {Bee b = new Bee();b.fly();System.out.println("Bee hava "+b.legNum+" legs");}}interface FlyAnimal{ void fly();}class Birds{int legNum = 2;public void egg() {}}class Insect{int legNum = 6;public void oviposition() {}}class Pigeon extends Birds implements FlyAnimal{public void fly() {System.out.println("pigenis are flyig");}}class Bee extends Insect implements FlyAnimal{public void fly() {System.out.println("Bee are flying");}}

(4)疑问:我定义了一个接口,但是我在继承这个接口的类中还要写接口的实现方法,那我不如直接就在这个类中写实现方法岂不是更便捷,还省去了定义接口?

挂了KFC的招牌,我们不用进去就知道他家卖哪些食物,就可以直接去点上校鸡块、黄金烤鸡腿堡。没有挂这个招牌,就算卖的东西和KFC一模一样,我们不进去看菜单就不会知道。没有KFC招牌,我们就要记住,中山路108号卖炸鸡,黄山路45号卖炸鸡(硬编码),很显然这样我们要记住的很多很多东西(代码量剧增),而且,如果有新的店卖炸鸡腿,我们也不可能知道(不利于扩展)。


13.内部类

(1)内部类是指在一个外部类的内部再定义一个类。类名不需要和文件夹相同。
(2)内部类可以是静态static的,也可用public,default,protected和private修饰。(而外部顶级类即类名和文件名相同的只能使用public和default)。

(3)内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。所以内部类的成员变量/方法名可以和外部类的相同。


(4)成员内部类

 成员内部类,就是作为外部类的成员,可以直接使用外部类的所有成员和方法,即使是private的。同时外部类要访问内部类的所有成员变量/方法,则需要通过内部类的对象来获取。

package test2;public class Outer {private int a = 10;public static void main(String[] args) {Outer o = new Outer();//创建外部类对象Outer.Inner i = o.new Inner();//创建内部类对象
//成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象i.eat();}public class Inner{private int a = 11;private int b = 5;public void eat() {System.out.println("eat "+ a +" eggs");//成员内部类可以无条件访问//外部类的所有成员属性和成员方法(包括private成员和静态成员)。System.out.println("eat "+ Outer.this.a +" eggs");//外部类.this.成员变量}}}

结果: eat 11 eggs

从结果可看出:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。  如果要访问外部的成员,用外部类.this.成员变量/方法

(5)局部内部类

局部内部类,是指内部类定义在方法和作用域内

(6)嵌套内部类

 嵌套内部类,就是修饰为static的内部类。声明为static的内部类,不需要内部类对象和外部类对象之间的联系,就是说我们可以直接引用outer.inner,即不需要创建外部类,也不需要创建内部类。
      嵌套类和普通的内部类还有一个区别:普通内部类不能有static数据和static属性,也不能包含嵌套类,但嵌套类可以。而嵌套类不能声明为private,一般声明为public,方便调用。

(7)匿名内部类

有时候我为了免去给内部类命名,便倾向于使用匿名内部类,因为它没有名字。

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

(8)静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。


(9)为什么在Java中需要内部类?总结一下主要有以下四点:
  1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,
  2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。
  3.方便编写事件驱动程序
  4.方便编写线程代码
  个人觉得第一点是最重要的原因之一,内部类的存在使得Java的多继承机制变得更加完善。


14.枚举类

枚举(enum)类型是Java 5新增的特性,它是一种新的类型,允许用常量来表示特定的数据片断,而且全部都以类型安全的形式来表示。

(1)常量的使用  :在JDK1.5之前,我们定义常量都是:public static fianl....。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

package test2;public class Outer {public enum Color{RED,BLACK,BLUE,YELLOW,GREEN,ORANGE,WHITE}public static void main(String[] args) {System.out.println(isRed(Color.BLACK));System.out.println(isRed(Color.RED));}static boolean isRed(Color color){if (Color.RED.equals(color)){return true;}elsereturn false;}}

(2).自定义函数

package test2;public class Outer {     public static void main(String[] args) {    for(Color color:Color.values()) { //  for (循环变量类型 循环变量名称 : 要被遍历的对象)  循环体    //枚举.values()表示得到全部的枚举内容,然后以对象数组的形式用foreach输出    System.out.println("name:"+color.getName()+" index:"+color.getIndex());    }    } }enum Color{RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);private String name;private int index;private Color(String name, int index) {this.name = name;this.index = index;}public String getName() {return name;}public void setName() {this.name= name;}public int getIndex() {return index;}public void setIndex() {this.index= index;}}

15.垃圾回收

(一).如何确定某个对象是“垃圾”?

如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法

这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方法(Python采用的是引用计数法)


(关于循环引用问题)

package test2;public class Outer {     public static void main(String[] args) {    Laji l1 = new Laji();    Laji l2 = new Laji();    l1.object = l2;    l2.object = l1;//最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,                   //导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。        l1 = null;    l2 = null;    }    } class Laji{public Object object = null;}

为了解决这个问题,在Java中采取了 可达性分析法。该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了


16.修饰符的适用范围

名称当前类同一个包子孙类其他包public√√√√protected√√√×friendly/default√√××private√×××

原创粉丝点击