Java编程思想扫盲

概述

本篇文章是出于对我的《Android内部分享》系列文章和视频中涉及到的Java知识, 考虑到很多朋友初次接触 Android 可能遇到的最大的困扰是 Java 的语法,面对很多视频和文章中的示例,无法理解语法规则何谈去理解 Android 开发,所以我特意做了这篇文章,希望能带你入坑,毕竟只是一篇文章,入坑后得靠自己去多看书,多练习,多思考。

C 和 Java

为什么说 Java 之前要提到 C 语言呢?我考虑的是很多语言其实都是 类C 语言,也就是说大多数语言的语法基础和 C 语言几乎一致,而且 C 语言是高校必学课程,从这一点入手可以事半功倍。

先来看一段 C 语言代码:

#include <stdio.h>

void testFunction(int);

int main(int argc, char const *argv[])
{
    int a = 1;
    testFunction(a);
}

void testFunction(int arg){
    printf("test : %d", arg);
}

这是一个很简单的 C 语言代码,里面定义了变量和函数,接下来我们用 Java 实现同样的功能,大家来对比一下:

public class TestJava1 {

    public static void main(String[] args){
        int a = 1;
        TestJava1 testJava1 = new TestJava1();
        testJava1.testFunction(a);
    }

    private void testFunction(int arg){
        System.out.println(String.format("test:%d", arg));
    }
}

可以发现有很多不一样的地方,但是其中也蕴含着很多一致的地方,我们对比一下来加深语法层面上的理解:

第一:入口函数的不同

在 c 语言中入口函数是非静态的(static 修饰),而在 Java 中入口函数式静态的。

而且这个静态的含义有很大不同,在 C 语言中添加静态就是只能在本文件中使用,无法跨文件。而在 Java 中则是属于类的静态函数,不需要创建类的实例即可调用。

第二:类和对象的概念

在 C 语言中可以直接调用该作用域下的函数,而在 Java 中任何方法和属性都属于某个类或者类的实例,所以需要先创建实例对象,才能通过实例对象调用该方法。

第三:变量的定义基本一致

在 C 语言和 Java 中定义变量是一样的,都是 类型 变量 = 默认值 的形式,但是 Java 中可以不手动设置默认值,每个类型都有对应的默认值,例如 int 类型默认值是 0, boolean 类型默认值是 false,对象类型默认值是 null.

有了上面的结论,我们可以发现 Java 最大的特点就在这个 面向对象 上面,这个也正是它的灵魂所在。

面向对象

面向对象是一种思想,Java 语法是这种思想的践行者。 C 语言最初是为了封装更接近机器语言的汇编语言,让我们开发起来更加方便,但是这种设计的初衷不是从人的思维模式来设计的,而是更加接近计算机的思考方式,这样就导致了一个问题,如果是比较简单的任务,我们可以很方便的利用计算机的思维逻辑,快速实现,而且运行速度比较快。但是如果我们要进行一个复杂的逻辑就比较难了,因为我们人很难想计算机一样考虑问题,我们所认识的世界和解决问题的方式和计算机有着本质的区别,所以这个时候人类容易理解的面向对象的思维方式就比较容易解决问题了。

面向对象中有一句经典的话:万物皆对象。

这句话阐明了对象是什么,对象就是任何东西,我们可以说我面前的这个女孩是对象,也可以说桌子上的杯子是对象,也可以说这个房子是对象,可以说任何事物都是对象,甚至我们可以说我上班的这件事情也是对象,这些对象实质上就是我们生活的世界里面的物和事。

面向对象的核心是抽象,我们如何将这些对象的共同特征抽象出来才是设计的本质,也是我们今后编程的基础,例如这个地球有很多人,小明,小红,小刚等等,他们都有共同的特征,我可以给所有人抽象出一些特征来表示他们,他们都有头发,眼睛鼻子,腿,胳膊,心脏等等,共同的特征是很多的,我们只需要抽象出我们需要用到的特征即可。

class Person{
    String name;
    String age;
}

这种具体物体的抽象比较好理解,假设我们要做一件事情也可以抽象称为一个类,例如我们去上班,我们可以把去上班这件事情抽象成类:

class DoWork{

    //具体上班的人
    Person mPerson;

    //去上班的路上
    void goToWay(){

    }

    //具体工作,写代码
    void writeCode(){

    }

    //回来的路上
    void backWay(){

    }
}

这样我们就可以指定让某个人去上班,然后执行对应的方法。

上面的 DoWork 类的对象和 Person 类的对象是一种 is-a-part 的组合关系,还有一种包含关系是 is-a 的关系,例如 学生 is-a 人, 翻译过来就是 Student extends Person:

class Person{
    String name;
    String age;
}

class Student extends Person{
    String studentId; //学号
    String sClass;  //年级
}

这就是一种简单的继承关系,这个是面向对象的重要特征之一,除了抽象和继承外,面向对象还有封装、多态两个特征:

封装:封装是面向对象和面向过程最大的不同,你只需要知道你想要的东西,无须知道具体内容和过程。比如你有一个手机需要充电,你此时只需要知道你的手机接口是什么形状(如usb),和电流参数即可无须知道别的,就可以充电。封装可以提高对象的安全程度,不需要暴露的东西私有化,你就无权访问。所以上面的代码我们将属性私有化,将需要暴露的暴露给外部:

class Person{
    protected String name;
    protected String age;
}

class Student extends Person{
    private String studentId; //学号
    private String sClass;  //年级

    public String getStudentId(){
        return studentId;
    }

    public String getName(){
        return name;
    }
}

多态:这个很容易理解,你可以说小明是个学生也可以说小明是个人。学生和人都是类,并且有继承关系,所以说多态是基于继承关系的。

Student xiaogang = new Student();
Person xiaohong = new Person();
Person xiaoming = new Student();

面向抽象编程

我们来思考一下,让我们去解决一件事情我们会怎么做?人类的思考方式应该从大的方向和模块上进行划分,也就是我们通常说的大步骤,例如去上班这件事:

第一步:明确上班时间和地点

第二步:用什么交通工具

第三步:带什么东西去(需要带电脑吗?)

第四步:做什么工作(工作内容)

你是不是会潜意识的去整体层面上作一思考,这其实就是面向抽象的思考问题,我们不需要确定一个具体的东西,从流程和关系上先做一个预演,这就有点想产品设计的过程。

所以说,我们在设计类的过程中也应该抽象出关系和流程,而不是具体的怎么做,怎么做可以交给具体的实现对象来做,这样就能可以应对更多的变化。

public interface DoWork {

    //去上班的路上
    void goToWay();

    //具体工作
    void doWork();

    //回来的路上
    void backWay();
}
public abstract class ProgramerDoWok implements DoWork {
    
    protected abstract void writeCode();

    @Override
    public void doWork() {
        writeCode();
    }
}
public class XiaoHongWrok extends ProgramerDoWok{

    @Override
    protected void writeCode() {
        System.out.println("写代码");
    }

    @Override
    public void goToWay() {
        System.out.println("骑自行车去上班");
    }

    @Override
    public void backWay() {
        System.out.println("坐公交下班");
    }
}

Java 的内部类

为什么要说这个内部类是因为 Android 开发中到处会遇到匿名内部类,这里特别解释一下什么是内部类,匿名内部类,局部内部类,静态内部类。

内部类

顾名思义,写到一个类的内部的类就是内部类:

public class OutClass{

    private String name;

    //内部类
    class InnerClass{

        //访问外部类的属性和方法
        public String getOutClassName(){
            return OutClass.this.name;
        }
    }
}

可以看到一个类的内部类是持有外部类的引用的(外部类的this),可以访问外部类的属性和方法。如果我们要创建 InnerClass 实例,先得创建 OutClass 实例。

OutClass outClass = new OutClass();
OutClass.InnerClass innerClass = outClass.new InnerClass();

静态内部类

如果我们给上面的内部类加一个 static 修饰符,则会发现不能访问外部类的属性了。一旦给内部类添加 static 修饰符,则该类不再持有外部类的引用,这个内部类和外部类的区别只是在作用域上有所不同。

OutClass.InnerClass innerClass = new OutClass.InnerClass();

局部内部类

局部内部类几乎不会用到,在一个类的方法内部声明的类叫做局部内部类,它可以访问该方法的 final 参数和外部类的成员属性,它的声明周期在该方法内部,外部不能创建和访问。

public class OutClass {

    private String name;

    private void doSomeThing(final String str, int a){
        class InnerClass{
            //static int b = 123; //报错:局部内部类不能有静态属性
            void function(){
                //局部内部类只能访问 final 的参数
                System.out.println("str = " + str);
                System.out.println("name = " + name);
            }
        }
        //System.out.println(InnerClass.b);
        InnerClass innerClass = new InnerClass();
        innerClass.function();
    }
}

匿名内部类

上面我们看到的内部类都有类名称,而匿名内部类是没有具体的名称的,它是一个抽象的实现,这个在 Android 中很常见。有点类似于 C 语言中的函数指针,将一个匿名对象传递给函数作为参数:

mListAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
        
    }
});

例如上面实际上是调用了 setOnItemClickListener 方法,它有一个参数是 BaseQuickAdapter.OnItemClickListener 对象实例,而这个 OnItemClickListener 是一个接口,在 Java 中一个抽象类和接口是不能创建具体实例的,所以我们需要实现它,上面代码和下面代码等价:

class MyTestClass implements BaseQuickAdapter.OnItemClickListener {
    @Override
    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
        
    }
}
mListAdapter.setOnItemClickListener(new MyTestClass());

上面这个 MyTestClass 的类声明貌似没有什么作用,因为这个类只创建一次实例,所以没必要具体定义这个类,我们就可以直接使用匿名内部类来简化代码。

包和作用域

Java中的包体现的是一个模块化和作用域控制,例如下图,按照功能划分为几个包:

我们如果要使用另外一个包内(非当前目录)的 Java 类就需要导入这个类:

package com.dlc.helloword.activity;

import com.dlc.helloword.adapter.ListAdapter;
import com.dlc.helloword.entry.ListEntry;
import com.dlc.helloword.util.ConvertUtil;
import com.dlc.helloword.util.ToastUtil;

public class ListAdapterTestActivity extends BaseActivity {

}

上面第一行是定义本类的包路径,而 3 到 6 行就是导入四个类,接下来我们就可以在 ListAdapterTestActivity 中使用这四个类了。

事实上,Java 中的类和方法的作用域是通过 包 路径和作用域共同决定的。

作用域当前类同一package(同包和不同包)子类其他package
public
protected×
default××
private×××
  • public:表明该成员变量或方法对所有类或对象都是可见的,所有类或对象都可以直接访问。
  • private:表明该成员变量或方法是私有的,只有当前类对其具有访问权限,除此之外的其他类或者对象都没有访问权限。
  • protected:表明该成员变量或方法对自己及其子类是可见的,即自己和(同包和不同包)子类具有权限访问。除此之外的其他类或者对象都没有访问权限。
  • default:表明该成员变量或方法只有自己和与其同一包内的类可见。

开启 Android Studio 的自动导包:

按照上图步骤,勾选自动导包选项,点击 Apply 按钮,然后再点击 Ok 关闭,这样 Android Studio 就可以帮你自动导入所需的包。

方法的重写和重载

重写也叫覆盖 就是 Override, 也就是覆盖掉的意思:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

class Animal{

   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{

   public void move(){
      System.out.println("狗可以跑和走");
   }
}

重载 overloading 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

public class Overloading {
    public int test(){
        System.out.println("test1");
        return 1;
    }
 
    public void test(int a){
        System.out.println("test2");
    }   
 
    //以下两个参数类型顺序不同
    public String test(int a, String s){
        System.out.println("test3");
        return "returntest3";
    }   
 
    public String test(String s, int a){
        System.out.println("test4");
        return "returntest4";
    }   
}