Java基础-面向对象

Java基础-面向对象

由于年前工作太忙、实在没时间整理Java基本知识。 但是可能长时间不更新不好看、再说我个人也没有半途而废的习惯。所以决定节前还是更新一篇吧。 这也算是Java的基本思想和一些偏进阶的东西。 如果思考透彻了、理解清楚了、那么这将对Java的进阶有很好的帮助。

1.面向对象

1.1面向对象的思想

A:面向过程思想概述
   写这一部分、我想了半天不晓得怎么比例子比较好、这样吧:一个人开车去北京、司机需要了解如何开锁、车轮如何转动、发动机内部哪个齿轮带动哪个齿轮、必须都清楚的搞明白,这就是面向过程的例子。
   那么什么是面向过程开发呢? 面向过程开发,其实就是面向着具体的每一个步骤和过程,把每一个步骤和过程完成,然后由这些功能方法相互调用,完成需求。 当需求单一,或者简单时,我们一步一步去操作没问题,并且效率也挺高。
   面向过程的代表语言:C语言
B:面向对象思想概述
    可随着需求的更改,功能的增多,发现需要面对每一个步骤很麻烦了。这时就开始思索,
    能不能把这些步骤和功能在进行封装,封装时根据不同的功能,进行不同的封装,功能类似的封装在一起。
    这样结构就清晰了很多。用的时候,找到对应的类就可以了。这就是面向对象的思想。
    再来个例子:还是开车的例子, 这时候开车去北京、你只需要告诉车这个对象,走。  而这时候你是不需要关注车子的发动机怎么工作的、 更不用关心发动机是如何带动车轮子转动的。 是不是美滋滋。 而车就是这个对象、 而车钥匙、刹车、油门、离合器、就是车这个对象给的接口。 
C:面向对象思想特点
    a:是一种更符合我们思想习惯的思想
    b:可以将复杂的事情简单化
    c:将我们从执行者变成了指挥者
        角色发生了转换

1.2 面向对象的特征

  • 有三个特征

    • 封装
      是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
    • 以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
    • 注意到gender属性使用int数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
        public class Person {
        
            private String name;
            private int gender;
            private int age;
        
            public String getName() {
                return name;
            }
        
            public String getGender() {
                return gender == 0 ? "man" : "woman";
            }
        
            public void work() {
                if (18 <= age && age <= 50) {
                    System.out.println(name + " is working very hard!");
                } else {
                    System.out.println(name + " can't work any more!");
                }
            }
        }
    
    • 封装好处
      • 隐藏实现细节,提供公共的访问方式
      • 提高代码复用性
      • 提高安全性[禁止对象之间的不良交互提高模块化]
    • 封装原则
      • 将不需要对外提供的内容都隐藏起来。
      • 把属性隐藏,提供公共方法对其访问。
      • 继承
      • 多态

1.3.类与对象及其应用

  • ** 描述现实世界事物**

  • 描述学生事务

    • 属性:姓名,年龄,性别
    • 行为:学习,吃饭,睡觉
    • 属性:就是该事物的描述信息【成员变量】
    • 行为:就是该事物能够做什么【成员方法】
  • ** 类和对象的概念**

    • a:类:是一组相关的属性和行为的集合
    • b:对象:是该类事物的具体体现
    • c:举例:
      • 类 学生
      • 对象 班长就是一个对象

1.4.对象的声明创建过程

  • 创建对象过程
    • (1):加载A.class文件进内存
    • (2):在栈内存为s开辟空间
    • (3):在堆内存为学生对象开辟空间
    • (4):对学生对象的成员变量进行默认初始化
    • (5):对学生对象的成员变量进行显示初始化
    • (6):通过构造方法对学生对象的成员变量赋值
    • (7):学生对象初始化完毕,把对象地址赋值给s变量

1.5.成员变量和局部变量的区别

  • 1.5.1 A:在类中的位置不同

    • 成员变量:在类中方法外
    • 局部变量:在方法定义中或者方法声明上
  • 1.5.2 B:在内存中的位置不同

    • 成员变量:在堆内存
    • 局部变量:在栈内存
  • 1.5.3 C:生命周期不同

    • 成员变量:随着对象的创建而存在,随着对象的消失而消失
    • 局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
  • 1.5.4 D:初始化值不同

    • 成员变量:有默认初始化值
    • 局部变量:没有默认初始化值,必须定义,赋值,然后才能使用。
    • 注意事项:: 局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则。

2.包和权限修饰符

2.1.包的定义及package

2.1.1 package关键字的概述及作用

  • A:包的概述: 就是文件夹
  • B:包的作用: 用来解决同一个路径下不能存在同名文件的问题(分类管理)
  • C:包的划分: 按照功能,按照模块

2.1.2 包的定义及注意事项

  • A:定义包的格式
    • package 包名;
    • 多级包用.分开即可
  • B:定义包的注意事项
    • A:package语句必须是程序的第一条可执行的代码
    • B:package语句在一个java文件中只能有一个
    • C:如果没有package,默认表示无包名

2.2.import关键字的概述和使用

2.2.1 A:导包的概述

  • 不同包下的类之间的访问,我们发现,每次使用不同包下的类的时候,都需要加包的全路径。比较麻烦。这个时候,java就提供了导包的功能

2.2.2 B:导包格式

  • import 包名;
  • 注意:
  • 这种方式导入是到类的名称。
  • 虽然可以最后写*,但是不建议。

2.3.四种权限修饰符的测试

  • 修饰符
    • 四种权限修饰符: private(私有的) , 默认 , protected(受保护的) , public(公共的)
  • 结论
*            本类   同一个包下  不同包下(子类) 不同包下(无关类)
* private      Y       
* 默认         Y       Y
* protected    Y       Y          Y
* public       Y       Y          Y           Y

2.4.类及其组成所使用的常见修饰符

  • A:修饰符:
    • 权限修饰符:private,默认的,protected,public
    • 状态修饰符:static,final
    • 抽象修饰符:abstract
  • B:类:
    • 权限修饰符:默认修饰符,public
    • 状态修饰符:final
    • 抽象修饰符:abstract
    • 用的最多的就是:public
  • C:成员变量:
    • 权限修饰符:private,默认的,protected,public
    • 状态修饰符:static,final
    • 用的最多的就是:private
  • D:构造方法:
    • 权限修饰符:private,默认的,protected,public
    • 用的最多的就是:public
  • E:成员方法:
    • 权限修饰符:private,默认的,protected,public
    • 状态修饰符:static,final
    • 抽象修饰符:abstract
    • 用的最多的就是:public
  • F:除此以外的组合规则:
    • 成员变量: public static final
    • 成员方法: public static / public abstract / public final

2.5.private关键字

  • private关键字特点
    • 是一个权限修饰符
    • 可以修饰成员变量和成员方法
    • 被其修饰的成员只能在本类中被访问
  • private最常见的应用
    • 把成员变量用private修饰,提供对应的getXxx()和setXxx()方法

3.Java 继承

3.1.继承简单概述

  • 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

3.2.继承格式

  • 通过extends关键字可以实现类与类的继承
  • class 子类名 extends 父类名 {}
  • 单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类

3.3.继承好处和弊端

  • 继承的好处
    • a:提高了代码的复用性
    • b:提高了代码的维护性
    • c:让类与类之间产生了关系,是多态的前提
  • 继承的弊端
    • 类的耦合性增强了。
    • 开发的原则:高内聚,低耦合。
    • 耦合:类与类的关系
    • 内聚:就是自己完成某件事情的能力

3.4.继承的注意事项

  • 继承的注意事项
    • a:子类只能继承父类所有非私有的成员(成员方法和成员变量)
    • b:子类不能继承父类的构造方法,但是可以通过super(待会儿讲)关键字去访问父类构造方法。
    • c:不要为了部分功能而去继承
  • 继承中构造方法的注意事项
    • 父类没有无参构造方法,子类怎么办?
      • a: 在父类中添加一个无参的构造方法
      • b:子类通过super去显示调用父类其他的带参的构造方法
      • c:子类通过this去调用本类的其他构造方法
      • 本类其他构造也必须首先访问了父类构造
    • B:注意事项
      • super(…)或者this(….)必须出现在第一条语句上

3.5.继承中成员变量的关系

  • 继承中成员变量的关系
    • A:子类中的成员变量和父类中的成员变量名称不一样
    • B:子类中的成员变量和父类中的成员变量名称一样
    • 在子类中访问一个变量的查找顺序(“就近原则”)
      • a: 在子类的方法的局部范围找,有就使用
      • b: 在子类的成员范围找,有就使用
      • c: 在父类的成员范围找,有就使用
        • d:如果还找不到,就报错

3.6.不支持多继承影响

  • Java 不支持多继承影响
    • Java 相比于其他面向对象语言,如 C++,设计上有一些基本区别,比如Java 不支持多继承。这种限制,在规范了代码实现的同时,也产生了一些局限性,影响着程序设计结构。Java 类可以实现多个接口,因为接口是抽象方法的集合,所以这是声明性的,但不能通过扩展多个抽象类来重用逻辑。
    • 在一些情况下存在特定场景,需要抽象出与具体实现、实例化无关的通用逻辑,或者纯调用关系的逻辑,但是使用传统的抽象类会陷入到单继承的窘境。
  • 为什么是单继承而不能多继承呢?
    • 若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承。
    • 多继承虽然能使子类同时拥有多个父类的特征,但是其缺点也是很显著的,主要有两方面:
      • 如果在一个子类继承的多个父类中拥有相同名字的实例变量,子类在引用该变量时将产生歧义,无法判断应该使用哪个父类的变量。
      • 如果在一个子类继承的多个父类中拥有相同方法,子类中有没有覆盖该方法,那么调用该方法时将产生歧义,无法判断应该调用哪个父类的方法。
    • Java是从C++语言上优化而来,而C++也是面向对象的,为什么它却可以多继承的呢?首先,C++语言是1983年在C语言的基础上推出的,Java语言是1995年推出的。其次,在C++被设计出来后,就会经常掉入多继承这个陷阱,虽然它也提出了相应的解决办法,但Java语言本着简单的原则舍弃了C++中的多继承,这样也会使程序更具安全性。
  • 为什么是多实现呢?
    • 通过实现接口拓展了类的功能,若实现的多个接口中有重复的方法也没关系,因为实现类中必须重写接口中的方法,所以调用时还是调用的实现类中重写的方法。

4.Java 多态

4.1.什么是多态

  • 什么是多态?
    • 多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。

4.2.多态的实现条件

  • 多态实现条件?
    • Java实现多态有三个必要条件:继承、重写、向上转型。
    • 继承:在多态中必须存在有继承关系的子类和父类。
    • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
    • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

4.3.多态实现方式

  • 多态的实现方式有哪些?
    • 多态作用:多态性就是相同的消息使得不同的类做出不同的响应。
  • 第一种实现方式:基于继承实现的多态
    • 基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。多态的表现就是不同的对象可以执行相同的行为,但是他们都需要通过自己的实现方式来执行,这就要得益于向上转型了。
    public class MainJava {
        public static void main(String[] args) {
            //定义父类数组
            Wine[] wines = new Wine[2];
            //定义两个子类
            Test1 test1 = new Test1();
            Test2 test2 = new Test2();
            Wine win e = new Wine();
            //父类引用子类对象
            wines[0] = test1;
            wines[1] = test2;
            for(int i = 0 ; i < 2 ; i++){
                System.out.println(wines[i].toString() + "--" + wines[i].drink());
            }
            System.out.println("-------------------------------");
            System.out.println(test1.toString() + "--" + test1.drink());
            System.out.println(test2.toString() + "--" + test2.drink());
        }
        public static class Wine {
            private String name;
            public String getName() {
                return name;
            }
            public void setName(String name) {
                this.name = name;
            }
            public String drink(){
                return "喝的是 " + getName();
            }
            public String toString(){
                return null;
            }
        }
    
        public static class Test1 extends Wine{
            public Test1(){
                setName("Test1");
            }
            public String drink(){
                return "喝的是 " + getName();
            }
            public String toString(){
                return "Wine : " + getName();
            }
        }
    
        public static class Test2 extends Wine{
            public Test2(){
                setName("Test2");
            }
            public String drink(){
                return "喝的是 " + getName();
            }
            public String toString(){
                return "Wine : " + getName();
            }
        }
    }
    
  • 第二种实现多态的方式:基于接口实现的多态
    • 继承是通过重写父类的同一方法的几个不同子类来体现的,那么就可就是通过实现接口并覆盖接口中同一方法的几不同的类体现的。
    • 在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
    • 继承都是单继承,只能为一组相关的类提供一致的服务接口。但是接口可以是多继承多实现,它能够利用一组相关或者不相关的接口进行组合与扩充,能够对外提供一致的服务接口。所以它相对于继承来说有更好的灵活性。

4.4.多态好处和弊端

  • 多态的好处

  • 多态的弊端

    • 不转型的话,不能使用子类特有的属性和行为
    class Demo_SuperMan {
        public static void main(String[]args){
            Person p=new SuperMan();//父类引用指向子类对象。超人提升为了人
                                    //父类引用指向子类对象,就是向上转型
            System.out.println(p.name);
            p.Tsy();
            //p.Fly();//找不到该方法
            SuperMan sm=(SuperMan)p;//向下转型,看到整个对象的内容
            sm.Fly();
        }
    }
     
    class Person{
        String name="John";
        public void Tsy(){
            System.out.println("Tsy");
        }
    }
     
    class SuperMan extends Person{
        String name="SuperName";
        @Override
        public void Tsy(){
            System.out.println("子类Tsy");
        }
     
        public void Fly(){
            System.out.println("飞出去救人");
        }
    }
    

5.Java 接口(Interface)

5.1.什么是接口

  • 接口(Interface)在Java语言中是一个抽象类型,是服务提供者和服务使用者之间的一个协议,在JDK1.8之前一直是抽象方法的集合,一个类通过实现接口从而来实现两者间的协议
  • 接口可以定义字段和方法。在JDK1.8之前,接口中所有的方法都是抽象的,从JDK1.8开始,也可以在接口中编写默认的和静态的方法。除非显式指定,否则接口方法都是抽象的

5.2.接口特点

- 从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
- 接口的特点有哪些呢?
    - 接口没有构造方法
    - 接口不能用于实例化对象
    - 接口中的字段必须初始化,并且隐式地设置为公有的、静态的和final的。因此,为了符合规范,接口中的字段名要全部大写
    - 接口不是被类继承,而是要被类实现
    - 接口中每一个方法默认是公有和抽象的,即接口中的方法会被隐式的指定为 **public abstract**。从JDK 1.8开始,可以在接口中编写默认的和静态的方法。声明默认方法需要使用关键字**default**。并且不允许定义为 private 或者 protected。
    - 当类实现接口时,类要实现接口中所有的方法。否则,类必须声明为抽象的
    - 接口支持多重继承,即可以继承多个接口

5.3.看一个接口案例代码

  • 代码如下所示
    public interface Name {
        
        //接口中的变量其实就是常量,默认被final修饰
        int age = 10;
    
    	String getName();
    	// 等价于以下三种形式
    	// public String getName();
    	// public abstract String getName();
    	// abstract String getName();
    
    	// 静态方法,可以省略public声明,因为在接口中的静态方法默认就是公有的
    	public static void setName(String name) {
    		// 实现具体业务
    	}
    	
    	// 默认方法
        default void defaultMethod(){
    		// 实现具体业务
    		System.out.println("defaultMethod");
    	}
    
    }
    

5.4.Marker Interface

  • 接口的职责也不仅仅限于抽象方法的集合,其实有各种不同的实践。
  • 有一类没有任何方法的接口,通常叫作 Marker Interface,顾名思义,它的目的就是为了声明某些东西,比如我们熟知的 Cloneable、Serializable 。这种用法,也存在于业界其他的 Java 产品代码中。

5.5.Java 8接口变化

  • 从 Java 8 开始,interface 增加了对 default method 的支持。Java 9 以后,甚至可以定义 private default method。Default method 提供了一种二进制兼容的扩展已有接口的办法。比如,我们熟知的 java.util.Collection,它是 collection 体系的 root interface,在 Java 8 中添加了一系列 default method,主要是增加 Lambda、Stream 相关的功能。
    public interface Collection<E> extends Iterable<E> {
         default Stream<E> stream() {
             return StreamSupport.stream(spliterator(), false);
         }
    }
    

6.Java 抽象类(abstract)

6.1.为何需要抽象类

  • 在面向对象的概念中,所有的对象都是通过类来描绘的。但并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类(Abstract)。

6.2.抽象类特点

  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

    • 抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。
    • 抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。其目的主要是代码重用。
    • 抽象类大多用于抽取相关 Java 类的共用方法实现或者是共同成员变量,然后通过继承的方式达到代码复用的目的。
    public abstract class AbstractClassExample {
    
        protected int x;
        private int y;
    
        public abstract void func1();
    
        public void func2() {
            System.out.println("func2");
        }
    }
    
    public class AbstractExtendClassExample extends AbstractClassExample {
        @Override
        public void func1() {
            System.out.println("func1");
        }
    }
    

6.3.抽象类可以new吗

- 注意抽象类是不能被实例化的,所以不可以实例化。

6.4.抽象类设计注意要点

  • 如果想要设计这样一个类,该类包含一个特别的成员方法,方法的具体实现由它的子类确定,那么可以在父类中声明该方法为抽象方法
  • Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。声明抽象方法会造成以下两个结果:
    • 如果一个类包含抽象方法,则该类必须声明为抽象类
    • 子类必须重写父类的抽象方法,否则自身也必须声明为抽象类

6.5.抽象类名作为形参

  • 案例
    /*抽象类作为参数的时候如何进行调用*/
    abstract class Animal {
        // 定义一个抽象方法
        public abstract void eat() ;
    }
    
    // 定义一个类
    class Cat extends Animal {
        public void eat(){
            System.out.println("吃.................") ;
        }
    }
    
    
    // 定义一个类
    class AnimalDemo {
        public void method(Animal a) {
            a.eat() ;
        }
    }
    
    // 测试类
    class ArgsDemo2  {
        public static void main(String[] args) {
            // 创建AnimalDemo的对象
            AnimalDemo ad = new AnimalDemo() ;
            // 对Animal进行间接实例化
            // Animal a = new Cat() ;
            Cat a = new Cat() ;
            // 调用method方法
            ad.method(a) ;
        }
    }
    

6.6.抽象类和接口

6.6.1.理解抽象和接口概念

  • abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。
  • abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,避免使用时在进行抽象类定义时对于 abstract class和interface的选择随意。
  • 其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

6.6.2.抽象和接口语法上不同

  • 抽象类
    abstract class Rect {    
        abstract void method1();    
        abstract void method2();    
    }    
    
  • 接口
    interface Rect {    
        void method1();    
        void method2();    
    }    
    
  • 在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

6.6.3.抽象和接口编程角度不同

  • abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。
  • 其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,不过在JDK1.8中可以使用default关键字实现默认方法。
    interface InterfaceA {
        default void foo() {
            System.out.println("InterfaceA foo");
        }
    }
    
  • 在 Java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为Java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。

6.6.4.通俗理解两者区别

  • 接口和抽象类的概念不一样。接口是对动作的抽象,抽象类是对根源的抽象。从设计理念上,接口反映的是 “like-a” 关系,抽象类反映的是 “is-a” 关系。
  • 抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。比如,男人,女人,这两个类(如果是类的话……),他们的抽象类是人。说明,他们都是人。
  • 人可以吃东西,狗也可以吃东西,你可以把“吃东西”定义成一个接口,然后让这些类去实现它.
  • 所以,在高级语言上,一个类只能继承一个类(抽象类)(正如人不可能同时是生物和非生物),但是可以实现多个接口(吃饭接口、走路接口)。

6.6.5.抽象类与接口的区别

区别
  • 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
    • 1.抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
    • 2.抽象类要被子类继承,接口要被类实现。
    • 3.接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
    • 4.抽象类里可以没有抽象方法。
    • 5.从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类,抽象类只能被单继承。
    • 6.接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法。
    • 7.抽象类不能在Java 8 的 lambda 表达式中使用。
    • 8.接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
    • 9.接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
如何选择
  • 使用接口:
    • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
    • 需要使用多重继承。
  • 使用抽象类:
    • 需要在几个相关的类中共享代码。
    • 需要能控制继承来的成员的访问权限,而不是都为 public。
    • 需要继承非静态和非常量字段。

6.6.7.面向对象之类名作为返回值类型

  • 案例
    /*具体类作为返回值类型*/
    class Student {
        public void show() {
            System.out.println("student....show.....................") ;
        }
    }
    
    // 定义一个类
    class StudentDemo {
        public Student getStudent() {
            return new Student() ;
        }
    }
    
    // 测试类
    class ReturnDemo {
        public static void main(String[] args) {
            // 创建StudentDemo的对象
            StudentDemo sd = new StudentDemo() ;
            // 调用 public Student getStudent()
            Student s = sd.getStudent() ;
            // 调用方法
            s.show() ;
        }
    }
    

6.6.8.面向对象之抽象类名作为返回值类型

  • 案例
    /*抽象类作为返回值类型*/
    abstract class Animal {
        public abstract void eat() ;
    }
    
    // 定义一个子类
    class Cat extends Animal {
        public void eat(){
            System.out.println("吃..........................") ;
        }
    }
    
    // 定义一个类
    class AnimalDemo {
        public static Animal getAnimal() {   
            // Animal a = new Cat() ;
            // return a;
            return new Cat() ;
        }   
    }
    
    // 测试类
    class ReturnDemo2  {
        public static void main(String[] args) {
            // 调用AnimalDemo的getAnimal这个方法
            Animal a =  AnimalDemo.getAnimal() ;
            // 调用方法
            a.eat() ;
        }
    }
    

6.6.9.面向对象之接口名作为返回值类型

  • 案例
    /*接口作为返回值类型*/
    interface Jump {
        public abstract void jump() ;
    }
    
    // 定义一个子类
    class JumpImpl implements Jump {
        public void jump(){
            System.out.println("jum....................") ;
        }
    }
    
    // 定义一个类
    class JumpDemo {
        public static Jump getJump() {
            return new JumpImpl() ;
        }
    }
    
    // 测试类
    class ReturnDemo3  {
        public static void main(String[] args) {
            // 调用getJump方法
            Jump jump = JumpDemo.getJump() ;
            // 调用
            jump.jump() ;
        }
    }
    

7.Java 内部类与匿名内部类

  • 7.1 什么是匿名对象

    • 就是没有名字的对象
  • 7.2 匿名对象应用场景

    • a:调用方法,仅仅只调用一次的时候。
    • b:匿名对象可以作为实际参数传递

8.Java 构造函数 和重载与覆盖

8.1.构造方法概述和作用

  • 构造方法概述和作用
    • 给对象中的成员进行初始化、一般如果我们不自己添加、则会有默认没有参数的构造方法。
  • 构造方法格式特点
    • 方法名与类名相同;没有返回值类型,连void都没有

8.2.构造方法注意事项

  • 构造方法注意事项
    • 如果我们没有给出构造方法,系统将自动提供一个无参构造方法。
    • 如果我们给出了构造方法,系统将不再提供默认的无参构造方法。
      • 注意:这个时候,如果我们还想使用无参构造方法,就必须自己给出。建议永远自己给出无参构造方法

8.3.看一个构造方法案例

  • 在用new关键字创建一个类的实例时,如果该类没有显式地编写一个构造函数,编译器会隐式地添加一个无参数的构造方法。
  • 例如来看一下下面的代码
    public class Base {
    	public Base(){
    		System.out.println("Base 的无参数的构造函数被调用");
    	}
    	public Base(String str){
    		System.out.println("Base 的带一个参数的构造函数被调用"+"--"+str);
    	}
    }
    
    public class Sub extends Base{
    	
    	public Sub(){
    		System.out.println("Sub 的无参数的构造函数被调用");
    	}
    	
    	public Sub(String str){
    		System.out.println("Sub 的带一个参数的构造函数被调用");
    	}
    	
    	public static void main(String[] args) {
    		Sub sub=new Sub();
    	}
    }
    
    • 运行结果为
    Base 的无参数的构造函数被调用
    Sub 的无参数的构造函数被调用
    
    • 可以用super关键字从子类的构造方法中显式地从父类的构造方法,即指定要执行父类的哪一个构造方法。此时super必须是子类构造方法中的第一条语句。将Sub类的无参构造方法修改为如下所示
    public Sub(){
    	super("Java");
    	System.out.println("Sub 的无参数的构造函数被调用");
    }
    
    • 运行结果如下所示:
    Base 的带一个参数的构造函数被调用--Java
    Sub 的无参数的构造函数被调用
    
    • 现在考虑一种情况。如果父类没有无参数的构造方法,但有一个含参数的构造方法
    public class Base {
    	public Base(String str){
    		System.out.println("Base 的带一个参数的构造函数被调用"+"--"+str);
    	}
    }
    
    • 此时子类没有显式调用父类含参数的构造方法,它的两个构造方法都会报错,因为编译器添加了对父类的无参数构造方法的一个隐式调用。但此时父类拥有一个含参数的构造函数,编译器也就不会隐式地为其添加一个无参数的构造方法,这就使得IDE报错
    public class Sub extends Base{
    	//会报错
    	public Sub(){
    		System.out.println("Sub 的无参数的构造函数被调用");
    	}
    	
    	//会报错
    	public Sub(String str){
    		System.out.println("Sub 的带一个参数的构造函数被调用");
    	}
    	
    	public static void main(String[] args) {
    		Sub sub=new Sub();
    	}
    }
    
    • 解决问题的方法有两个,可以为父类添加一个无参构造函数,或者使子类的构造函数显示调用父类的有参构造函数。

8.4.构造方法加载机制

  • 当通过调用子类的构造方法来实例化一个子类时,该构造方法会首先调用直接父类的无参数的构造方法。而父类的构造方法也会调用其直接父类的构造方法,这个过程不断重复,直到到达了java.lang.Object类的构造方法,即子类的所有父类也会实例化。

8.5.什么是方法重写?

  • A:什么是方法重写
    • 子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也被称为方法覆盖,方法复写、也叫做重写。顾名思义就是不想用父类的方法内容、自己又重写。
  • B:方法重写的应用
    • 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法。
    • 这样,即沿袭了父类的功能,又定义了子类特有的内容。
  • C:方法重写注意事项
    • a:父类中私有方法不能被重写 ,因为父类私有方法子类根本就无法继承
    • b:子类重写父类方法时,访问权限不能更低 ,最好就一致
    • c:父类静态方法,子类也必须通过静态方法进行重写 ,其实这个算不上方法重写,但是现象确实如此,至于为什么算不上方法重写,多态中我会讲解
  • 覆盖方法作用是什么
    • 覆盖一个方法就会修改其行为。要覆盖一个方法,直接在子类中编写新的方法,而不必修改其父类的任何内容。
    • 可以覆盖父类的公开的和受保护的方法。如果子类和父类位于相同的包中,还可以覆盖带有默认访问级别的方法,因为此时父类的默认访问级别的方法对子类来说才是可见的。而私有方法对其他类来说都是不可见的,所以无法覆盖。 还有如果使用final 字段修饰的方法 不会被覆盖。

8.6.什么是方法重载?

  • A.什么是方法重载?
    • 一个类中,有若干个方法名字相同,但方法参数形式不同,成为方法重载。 例如有默认类名的构造函数、还可以重载类名构造函数方法。
  • B.方法重载原则?
    • 方法名相同;方法的参数类型不同,或者参数个数不同;与方法的参数名,返回类型和修饰符无关

8.7.父类的静态方法能否被子类重写

  • 思考一下:父类的静态方法能否被子类重写?
    • 父类的静态方法是不能被子类重写的,其实重写只能适用于实例方法,不能用于静态方法,对于上面这种静态方法而言,我们应该称之为隐藏。

9.形参与实参 值传递与引用传递

9.1.基本类型和引用类型

  • Java中的数据类型分为两种为基本类型和引用类型。
    • 1、基本类型的变量保存原始值,所以变量就是数据本身。
      • 常见的基本类型: byte,short,int,long,char,float,double,Boolean,return Address
    • 2、引用类型的变量保存引用值,所谓的引用值就是对象所在内存空间的“首地址值”,通过对这个引用值来操作对象。
      • 常见的引用类型:类类型,接口类型和数组。
  • 基本类型(primitive types)
    • primitive types 包括boolean类型以及数值类型(numeric types)。numeric types又分为整型(integer types)和浮点型(floating-point type)。整型有5种:byte short int long char(char本质上是一种特殊的int)。浮点类型有float和double。
  • 引用类型(reference types)
    • ①接口 ②类 ③数组
  • 基本数据类型和引用数据类型传递区别
    • 程序设计语言中有关参数传递给方法的两个专业术语是:
      • 按值调用:表示方法接收的是调用者提供的值。
      • 按引用调用:表示方法接收的是调用者提供的变量的地址。
    • 在Java中 不存在按引用调用,也就是说,假如方法传递的是一个引用数据类型,那么可以修改 引用所指向的对象的属性,但不能 让引用指向其它的对象。
    • 传递基本数据类型
      public static void methodBasic() {
          int lNum = 3;
          methodRunInner(lNum);
          Log.d("CopySample", "After methodRunInner, lNum=" + lNum);
      }
      
      private static void methodRunInner(int lNum) {
          lNum++;
          Log.d("CopySample", "methodRunInner, lNum=" + lNum);
      }
      
    • 传递引用数据类型
      • 当传递的是引用数据类型,可以在函数中修改该引用所指向的对象的成员变量的值,如下所示:
      public static void methodRef() {
          NumHolder holder = new NumHolder();
          holder.num = 3;
          methodRunInner(holder);
          Log.d("CopySample", "After methodRunInner, holder.num=" + holder.num);
      }
      
      private static void methodRunInner(NumHolder holder) {
          holder.num++;
          Log.d("CopySample", "methodRunInner, holder.num=" + holder.num);
      }
      
      private static class NumHolder {
          int num;
      }
      

9.2.什么是值传递

  • 在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。
  • 比如String类,设计成不可变的,所以每次赋值都是重新创建一个新的对象,因此是值传递!

9.3.什么是引用传递

  • 引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间。
  • 引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。
  • 在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。

9.4.通过案例分析

  • 看下面代码案例
    private void test1(){
        Demo demo = new Demo();
        demo.change(demo.str, demo.ch);
        Log.d("yc---",demo.str);
        Log.d("yc---", Arrays.toString(demo.ch));
        //打印值
        //yc---: hello
        //yc---: [c, b]
    }
    
    public class Demo {
        String str = new String("hello");
        char[] ch = {'a', 'b'};
        public void change(String str, char[] ch) {
            str = "ok";
            ch[0] = 'c';
        }
    }
    

9.5.得出的结论

  • 通过上面的分析我们可以得出以下结论:
    • 基本数据类型传值,对形参的修改不会影响实参;
    • 引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象。
    • String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。
  • 如何理解引用类型的按值传递?
    • 引用类型的按值传递,传递的是对象的地址。只是得到元素的地址值,并没有复制元素。比如数组,就是引用传递,假如说是值传递,那么在方法调用赋值中,将实参的值复制一份传递到函数中将会非常影响效率。

10.代码块

  • A:代码块概述
    • 在Java中,使用{}括起来的代码被称为代码块。
  • B:代码块分类
    • 根据其位置和声明的不同,可以分为局部代码块,构造代码块,静态代码块,同步代码块。
  • C:常见代码块的应用
    • a:局部代码块
      • 在方法中出现;限定变量生命周期,及早释放,提高内存利用率
    • b:构造代码块
      • 在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行
    • c:静态代码块
      • 在类中方法外出现,加了static修饰
      • 在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且只执行一次。

11.面向对象六大原则

11.1 单一职责原则

  • 指一个类的功能要单一,不能包罗万象。越简单越好、其实方法的内容和类的内容一样遵循该原则。

11.2 开放封闭原则

  • 指一个模块在扩展性方面应是开放的,在更改性方面应是封闭的

11.3 替换原则

  • 子类应当可以替换父类,并出现在父类能够出现的任何位置

11.4 依赖原则

  • 具体依赖抽象,上层依赖下层

11.5 接口分离原则

  • 模块间要通过抽象接口隔开,而不是通过具体的类强行耦合起来。

11.6 迪米特法则

  • 又称最少知道原则(Demeter Principle,DP)
    • 最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。从软件工程角度讲、是尽可能的减少类与类之间的耦合。

12.总结

写了这么多的知识点、总结了这么多、有自己写的、也有从网络上找来的、因为一些死的东西基本上是不会改变的,所以我就信手拈来了。还有一些例子比起来找不到更合适的,所以也有一些借鉴。

这么多的知识点、需要日常开发中阅读代码、编写代码慢慢思考才可以搞起来、灵活变通。 有问题我们评论区见。

发布了43 篇原创文章 · 获赞 24 · 访问量 14万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览