java之面向对象详解(转)

#############java面向对象详解#############
1、面向对象基本概念
2、类与对象
3、类和对象的定义格式
4、对象与内存分析
5、封装性
6、构造方法
7、this关键字
8、值传递与引用传递?
9、对象的一对一关系
10、static关键字
11、main方法分析
12、继承
13、对象的初始化
14、方法的重写
15、super关键字
16、final关键字
17、抽象类
18、接口
19、多态性
20、instanceof关键字
21、内部类
######################################
1、面向对象基本概念
其本质是以建立模型体现出来的抽象思维过程和面向对象的方法(百度百科)
是一种编程思维,也是一种思考问题的方式

如何建立面向对象的思维呢?
1、先整体,再局部
2、先抽象,再具体
3、能做什么,再怎么做

2、类与对象
类:类是一种分类,一个类别,一个模板,它描述一类对象的行为和状态,是一组具有相同特性(属性)与行为(方法)的事物集合
对象:是一个个性的产物,是一个个体的特征,是类的一个实例,有状态和行为

3、类和对象的定义格式
类的定义:
class 类名称{
属性名称;
返回值类型 方法名称(){}
}

对象的定义:
一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下:
类名称 对象名称 = new 类名称() ;

如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式:
访问类中的属性:对象.属性 ;
调用类中的方法:对象.方法();

在java中对象声明有两种含义
声明对象:Horse horse= null; ; //表示声明了一个对象,但是此对象无法使用,horse没有具体的内存指向
实例化对象:horse= new Horse() ;// 表示实例化了对象,可以使用

horse.eat(); //通过对象调用方法
new Horse().eat();//匿名对象调用方法


4、对象与内存分析

new 关键字表示创建一个对象
new 关键字表示实例化对象
new 关键字表示申请内存空间
注意:如果使用一个没有申请内存空间的对象,会报空指针异常:java.lang.NullPointerException

 

 

(1)new关键字:表示向内存申请空间,也表示实例化一个对象,创建一个对象。
(2)一个对象在内存中的大小,由该对象的所有属性所占的内存大小的总和。引用类型变量在32位系统上占4个字节,在64位系统上占8个字节。加上而外的对象隐性数据所占的大小。
(3)相同的类型才可以赋值
(4)不同的引用,指向同一个对象,任何一个引用改变对象的值,其它引用都会反映出来。
(5)编程时要注意的问题,在确定不使用对象时,要尽早释放对象:引用=null
(6)当一个堆中的对象没有被任何引用变量所指向时,该对象会被JVM 的 GC 程序认为是垃圾对象,从而被回收

 

5、封装性
封装性的概念
封装性是面向对象思想的三大特征之一,封装就是隐藏实现细节,仅对外提供访问接口。实现细节部份包装、隐藏起来的方法。
封装有:属性的封装、方法的封装、类的封装、组件的封装、模块化封装、系统级封装…
封装的好处:模块化、信息隐藏、代码重用、插件化易于调试、具有安全性
封装的缺点:会影响执行效率

封装之前:属性都可以直接访问和修改
class Person{
  String name;
  int age;
}

封装之后:
class Person{
  //属性是成员变量,私有化属性,使得其他对象不能直接访问属性
  private String name;
  private int age;
  //参数及方法内定义的变量是局部变量
  public void setName(String name){
  this.name = name;}
  public String getName(){
  return name;}
}

成员变量和局部变量的区别
a、在类中的位置不同
成员变量:在类中定义
局部变量:在方法中定义或者方法的参数
b、在内存中的位置不同
成员变量:在堆内存(成员变量属于对象,对象进堆内存)
局部变量:在栈内存(局部变量属于方法,方法进栈内存)
c、生命周期不同
成员变量:随着对象的创建而存在,随着对象的销毁而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
d、初始化值不同
成员变量:有默认初始化值,引用类型默认为null
局部变量:没有默认初始化值,必须定义,赋值,然后才能使用
注意:
局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则。

6、构造方法
无参构造方法:
public Dog(){} //如果一个类没有定义构造方法,则默认无无参构造,如果有定义有参构造,最好再显示定义一个无参构造方法

带参构造方法:
public Dog(String name){
this.name = name;
}

多参构造方法:
public Dog(String name,int age){
this.name = name;
this.age = age;
}

(1)构造方法名称与类名相同,没有返回值声明(包括 void)
(2)构造方法用于初始化数据(属性)
(3)每一个类中都会有一个默认的无参的构造方法
(4)如果类中有显示的构造方法,那么默认构造方法将无效
(5)如果有显示的构造方法,还想保留默认构造 方法,需要显示的写出来。
(6)构造方法可以有多个,但参数不一样,称为构造方法的重载
(7)在构造方法中调用另一个构造方法,使用this(...),该句代码必须在第一句。
(8)构造方法之间的调用,必须要有出口。
(9)给对象初始化数据可以使用构造方法或setter方法,通常情况下,两者都会保留。
(10)一个好的编程习惯是要保留默认的构造方法。(为了方便一些框架代码使用反射来创建对象)
(11)private Dog(){},构造方法私有化,当我们的需求是为了 保正该类只有一个对象时(单例模式就是私有化构造器)。

7、this关键字
this关键字指向的是当前对象的引用
调用类中的属性:this.属性名称,指的是访问类中的成员变量,用来区分成员变量和局部变量(重名问题)
调用类中的方法:this.方法名称,用来访问本类的成员方法
调用类构造方法:this();访问本类的构造方法,()中可以有参数的 如果有参数 就是调用指定的有参构造
注意:
1.this() 不能使用在普通方法中,只能写在构造方法中
2.必须是构造方法中的第一条语句

 

8、值传递与引用传递?

首先,注意:在java中只有按值传递,并没有所谓的按引用传递
java数据类型可以分为两大类:基本类型(primitive types)和引用类型(reference types)

 

 

 

a、基本数据类型的按值传递

public class Swap {
public static void main(String[] args) {
  int x = 10;
  int y = 20;
  swap(x, y);
  System.out.println("x(2) = " + x);
  System.out.println("y(2) = " + y);
}
public static void swap(int x, int y) {
  int temp = x;
  x = y;
  y = temp;
  System.out.println("x(1) = " + x);
  System.out.println("y(1) = " + y);
}
}
/*输出
x(1) = 20
y(1) = 10
x(2) = 10
y(2) = 20
*/

main函数调用swap函数来交换 x,y的值,然而调用函数之后发现main中x,y的值并未交换。包括在Java api中找不到一个可以交换两个变量的方法。这与Java语言的特性有关。通过一个图就可以知道上面程序的运行结果了。

 

main函数中的x,y和swap函数中的x,y分别存放在不同的区域,在main中调用swap函数的时候,会将main中的x,y的值赋给swap中的x,y。当swap函数中对x,y交换时只是对swap帧中的x,y做交换,并不会改变main中的x,y。所以当函数返回时main中的x,y并不会改变

 

b、引用数据类型的按值传递
引用数据数据类型分为三种:①接口 ②类 ③数组

public static void main(String[] args) {
  int []a={10,20};
  System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=10,a[1]=20;
  swap(a, 0, 1);
  System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
public static void swap(int []a,int i,int j){
  nt temp=a[i];
  a[i]=a[j];
  a[j]=temp;
  System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
//输出
/*a[0]=10 a[1]=20
a[0]=20 a[1]=10
a[0]=20 a[1]=10
*/

运行程序后发现,swap函数对a[0] ,a[1]的操作竟然影响到了main函数中的a[0] ,a[1]的值,真是不可思议。为什么会产生如此之结果。原来引用类型的按值传递,传递的是对象的地址

由图可以看出在swap中仅仅是得到了数组的地址,并没有对数组的元素进行复制,在swap中对数组的操作是直接对main函数中数组的操作,因此swap函数返回后main函数中的a[0] ,a[1]的值发生交换

引用https://blog.csdn.net/u013309870/article/details/75499175

 

12、继承
继承是面向对象三大特征之一
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类

被继承的类称为父类(超类),继承父类的类称为子类(派生类)
通过继承可以实现代码重用
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
构造器而言,它只能够被调用,而不能被继承,可以通过使用super()进行调用,
对于继承而已,子类会默认调用父类的构造器,但是如果没有默认的父类构造器,子类必须要显示的指定父类的构造器(通过super()),而且必须是在子类构造器中做的第一件事(第一行代码)。
对于protected而言,它指明就类用户而言,他是private,但是对于任何继承与此类的子类而言或者其他任何位于同一个包的类而言,他却是可以访问的
Java 的继承是单继承,但是可以多重继承,


语法:[访问权限] class 子类名 extends 父类名{
  类体定义;
}

示例:
public class Dog{
    private String name;
    private String sex;
    public void eat(){System.out.println(“吃饭”);}
}


public class HomeDog extends Dog{
  //类的定义
}


public class HuskyDog extends Dog{
  //类的定义
}

public class GoldenRetrieverDog extends HomeDog {
  //类的定义
}

protected(受保护的访问权限修饰符,用于修饰属性和方法,使用protected修饰的属性和方法可以被子类继承)


(1)继承是发生在多个类之间
(2)继承使用关键字extends
(3)JAVA只能单继承,允许多层继承,但可以实现多个接口
(4)被继承的类叫父类(超类),继承父类的类叫子类(派生类)
(5)在父类中的非私有属性和方法可以被子类继承
(6)protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承
(7)构造方法不能被继承
(8)创建对象会调用构造方法,调用构造方法不一定就是创建对象
(9)实例化子类对象,会先调用父类的构造方法,如果父类中没有默认的构造方法,那么子类必须显示的通过super(...)来调用父类的带参构造方法,super也只能在子类构造方法中的第一句

继承的好处:
a、提高代码的复用性
b、提高代码的维护性
c、让类与类之间产生关系,是多态的前提
继承的缺点:增强了类与类之间的耦合性

 

 13、对象初始化

public class CodeBlockTest {
  public static void main(String[] args) {
  Child child = new Child();
}
}
class Father {
  public static String fatherStr1 = "fatherStr1(静态字段初始化值)";

  public String fatherStr2 = "fatherStr2(字段初始化值)";
  static {
    System.out.println("父类静态代码块:" + fatherStr1);
    fatherStr1 = "fatherStr1(静态代码块赋值)";
  }
  {
    System.out.println("父类构造代码块:" + fatherStr2);
    fatherStr2 = "fatherStr2(构造代码块赋值)";
  }
  public Father() {
  System.out.println("父类构造函数块:" + fatherStr2);
  fatherStr2 = "fatherStr2(构造函数赋值)";
  }
}


class Child extends Father {
  public static String childStr1 = "childStr1(静态字段初始化值)";
  public String childStr2 = "childStr2(字段初始化值)";
  static {
    System.out.println("子类静态代码块:" + childStr1);
    childStr1 = "childStr1(静态代码块赋值)";
  }
  {
    System.out.println("子类构造代码块:" + childStr2);
    childStr2 = "childStr2(构造代码块赋值)";
  }
  public Child() {
  System.out.println("子类构造函数:" + childStr2);
  childStr2 = "childStr2(构造函数赋值)";
  }
}
// 输出结果:
// 父类静态代码块:fatherStr1(静态字段初始化值)
// 子类静态代码块:childStr1(静态字段初始化值)
// 父类构造代码块:fatherStr2(字段初始化值)
// 父类构造函数块:fatherStr2(构造代码块赋值)
// 子类构造代码块:childStr2(字段初始化值)
// 子类构造函数:childStr2(构造代码块赋值)

 

通过每执行一个代码块或构造函数,输出字段在上一代码块执行后的值,以此来探究对象的初始化顺序。
由目前的输出结果可知,对于对象的初始化顺序,我们可以得出以下结论:
1. 父类静态字段初始化
2. 父类静态代码块、子类静态字段初始化 (接下来探究两者的顺序)
3. 子类静态代码块
4. 父类普通字段初始化
5. 父类构造代码块
6. 父类构造函数
7. 子类普通字段初始化
8. 子类构造代码块
9. 子类构造函数

public class CodeBloacTest2 {
  public static void main(String[] args) {
  Child child = new Child();
}
}
class Father {
  public static String fatherStr = "(静态字段初始化值)";
  static {
    System.out.println("父类静态代码块:fatherStr" + fatherStr);
    fatherStr = "(静态代码块赋值)";
  }
}


class Child extends Father {
  public static String childStr = fatherStr;
  static {
    System.out.println("子类静态代码块:childStr = fatherStr" + childStr);
    childStr = "(静态代码块赋值)";
  }
}
// 输出结果:
// 父类静态代码块:fatherStr(静态字段初始化值)
// 子类静态代码块:childStr = fatherStr(静态代码块赋值

我们在子类静态字段childStr初始化的时候,赋的是父类静态字段fatherStr的值。由输出结果可知,childStr初始化后的值是父类静态代码块执行后赋予fatherStr的值。由此可知两者的执行顺序为:父类静态代码块==>子类静态字段初始化

 

初始化顺序(最终结果):

父类静态字段初始化
父类静态代码块
子类静态字段初始化
子类静态代码块
父类普通字段初始化
父类构造代码块
父类构造函数
子类普通字段初始化
子类构造代码块
子类构造函数

引用https://www.jb51.net/article/111157.htm

 

14、方法的重写
方法重写(overriding method)
在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想做一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
在子类和父类中,重写方法后,在调用时,以创建的对象类型为准,会调用谁的方法。


重写特性:
a、发生在子父类中,方法重写的两个方法返回值、方法名、参数列表必须完全一致(子类重写父类的方法)
b、子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常)
c、子类方法的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
d、父类中的方法若使用private、static、final任意修饰符修饰,那么,不能被子类重写。

   public class Test{
       public void print(){
           System.out.println("this is test!");
       }
   }

    public class TestA extends Test{
        @Override
        public void print(){
            System.out.println("this is testA!");
        }
    }

    public class TestB extends Test{
        @Override
        public void print(){
            System.out.println("this is testB!");
        }
    }

 

面试题 :overloading与overriding的区别?

15、super关键字
可以理解为对父类的引用,使用super来调用父类的属性,方法,和构造方法
super可以完成以下的操作:
a、使用super调用父类中的属性,可以从父类实例处获得信息。
b、使用super调用父类中的方法,可以委托父类对象帮助完成某件事情。
c、使用super调用父类中的构造方法(super(实参)形式),必须在子类构造方法的第一条语句,调用父类中相应的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super();

 

16、final关键字
使用final关键字完成以下的操作:
a、使用final关键字声明一个常量
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的
b、使用final关键字声明一个方法
该方法为最终方法,且只能被子类继承,但是不能被子类重写。
c、使用final关键字声明一个类
该类就转变为最终类,没有子类的类,fianl修饰的类无法被继承。
d、在方法参数中使用final,在该方法内部不能修改参数的值(在内部类中详解)

当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。

可以参考:https://www.cnblogs.com/xiaoxi/p/6392154.html

 

17、抽象类
抽象类的基本概念
(1)很多具有相同特征和行为的对象可以抽象为一个类;很多具有相同特征和行为的类可以抽象为一个抽象类。
(2)使用abstract关键字声明的类为抽象类

定义一个抽象类

abstract class Animal{
  public abstract void move();
}
abstract class Person extends Animal{
  private String name;
  //...
  public abstract void eat();//抽象方法
}

抽象类的规则:
a、抽象类可以没有抽象方法,有抽象方法的类必须是抽象类
b、非抽象类继承抽象类必须实现所有抽象方法
c、抽象类可以继承抽象类,可以不实现父类抽象方法。
d、抽象类可以有方法实现和属性
e、抽象类不能被实例化
f、抽象类不能声明为final
g、抽象类可以有构造方法

18、接口
接口的定义格式:
interface 接口名称{
  全局常量 ;
  抽象方法 ;
}
示列:
interface IEat{
  //public abstract void eat();
  void eat();  //默认为public abstract void eat();
  //public static final int NUM = 10;
  int NUM = 10; }

interface ISleep extends IEat{
void sleep();
}
接口的使用规则:
(1)定义一个接口,使用interface关键字
(2)在一个接口中,只能定义常量、抽象方法,JDK1.8后可以定义默认的实现方法
(3)接口可以继承多个接口:extends xxx,xxx
(4)一个具体类实现接口使用implements关键字
(5)一个类可以实现多个接口
(6)抽象类实现接口可以不实现接口的方法
(7)在接口中定义的方法没有声明 访问修饰符,默认为public
(8)接口不能有构造方法
(9)接口不能被实例化
java8新增
(1)增加了default方法和static方法,这两种方法完全可以有方法体
(2)default方法属于实例,static方法属于类(接口)
(3)接口中的静态方法不会被继承,接口中的静态变量会被继承

public interface IUser {
  static void say() {
  System.out.println("say_" + IUser.class);
 }

  default void eat() {
  System.out.println("eat_" + IUser.class);
 }
}

 

19、多态性

多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:

多态的优点
    1. 消除类型之间的耦合关系
    2. 可替换性
    3. 可扩充性
    4. 接口性
    5. 灵活性
    6. 简化性
多态存在的三个必要条件
    继承
    重写

@Test
    public void test() throws Exception {
        Shape shape = null;
        String condition = "画圆";
        if (condition.equalsIgnoreCase("画圆")) {
            shape = new Circle();
        } else if (condition.equalsIgnoreCase("画正方形")) {
            shape = new Square();
        } else if (condition.equalsIgnoreCase("画三角形")) {
            shape = new Triangle();
        } else {
            throw new Exception("未找到对应的画图对象");
        }

        //画东西
        shape.draw();
    }

    /**
     * 基类
     */
    abstract class Shape {
        /**
         * 画东西
         */
        void draw() {
        }
    }

    /**
     * 画圆
     */
    class Circle extends Shape {
        void draw() {
            System.out.println("Circle.draw()");
        }
    }

    /**
     * 画正方形
     */
    class Square extends Shape {
        void draw() {
            System.out.println("Square.draw()");
        }
    }

    /**
     * 画三角形
     */
    class Triangle extends Shape {
        void draw() {
            System.out.println("Triangle.draw()");
        }
    }

 

 

20、instanceof关键字
instanceof 是用于检查对象是否为指定的类型,通常在把父类引用强制转换为子类引用时要使用,以避免发生类型转换异常(ClassCastException)。

语法格式如下:
对象 instanceof 类型--返回boolean类型值

示例:
if(homeChicken instanceof Chicken){
  //...
}
该语句一般用于判断一个对象是否为某个类的实例,是返回true,否返回false
父类的设计法则
通过instanceof关键字,我们可以很方便的检查对象的类型,但如果一个父类的子类过多,这样的判断还是显得很繁琐,那么如何去设计一个父类呢?
a、父类通常情况下都设计为抽象类或接口,其中优先考虑接口,如接口不能满足才考虑抽象类。

b、一个具体的类尽可能不去继承另一个具体类,这样的好处是无需检查对象是否为父类的对象。

  

原文转自:https://www.cnblogs.com/zsql/p/11115244.html

posted @ 2021-07-29 16:02  有容乃大  阅读(122)  评论(0编辑  收藏  举报