Java基础知识(二)

一、对象与类

要认识到重要的一点,对象变量并没有包含一个对象,他只是引用一个对象。在Java中,任何对象变量的值都是对存储在另外一个地方的某个对象的引用,可以显式地将对象变量设置为null,指示这个对象变量目前没有引用任何对象。

可以用public标记实例字段,但这是一种很不好的做法。public数据字段允许程序中任何方法对其进行读取和修改,这就完全破坏了封装。因此,这里强烈建议将实例字段标记为private。

静态字段

public class Data {
    public static int id = 1;
}

所有的Data对象共享一个id字段。它属于类,而不属于任何单个对象。在一些面向对象的程序设计语言当中,静态字段被称为类字段

静态方法

可以使用对象来调用静态方法,但是这种写法可能会造成混淆,我们建议使用类名而不是对象来调用静态方法。
在下面两种情况下可以使用静态方法:

  • 方法不需要访问对象状态,因为它所需要的所有参数都通过显式参数传递
  • 方法只需要访问类的静态字段

方法参数

首先回顾一下在程序设计语言中关于如何将参数传递给方法(或函数)的一些专业术语。按值传递(call by value)表示方法接收的是调用者提供的值。而按引用传递(call by reference)表示方法接收的是调用者提供的变量地址。方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。
Java语言总是采用按值传递。也就是说,方法得到的是所有参数值的一个副本。具体来讲,方法不能修改传递给它的任何变量参数的内容。很多程序设计语言(特别是C++和Pascal)提供了两种参数传递的方式:按值传递和按引用传递。有些程序员认为Java程序设计语言采用的是按引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所有很有必要给出一个反例来详细地说明一下这个问题。

public class MethodTest {
    public static void main(String[] args) {
        Person person = new Person("Alice");
        Person person1 = new Person("Bob");
        System.out.println("before " + person.name + " " + person1.name);
        swap(person, person1);
        System.out.println("after " + person.name + " " + person1.name);
    }

    public static void swap(Person person, Person person1) {
        Person temp = person;
        person = person1;
        person1 = temp;
        System.out.println("swap " + person.name + " " + person1.name);
    }

    public static class Person {
        public String name;

        public Person(String name) {
            this.name = name;
        }
    }
}

输出:

before Alice Bob
swap Bob Alice
after Alice Bob

可以看到在方法结束时参数变量被丢弃了,原来的变量仍然引用这个方法调用之前所引用的对象。
这个过程说明:Java程序设计语言对对象采用的不是按引用传递,实际上,对象引用是按值传递的
下面总结一下Java中对方法参数能做什么和不能做什么:

  • 方法不能修改基本数据类型的参数(即数值型或布尔型)
  • 方法可以改变对象参数状态
  • 方法不能让一个对象参数引用一个新的对象

方法重载

Java允许重载任何方法,因此,要完整地描述一个方法,需要指定方法名以及参数类型,这叫做方法的签名(signature)。例如String类有4个名为index的公共方法。它们的签名是

indexOf(int)
indexOf(int, int)
indexOf(String)
indexOf(String, int)

返回类型不是方法签名的一部分,也就是说,不能有两个名字相同、参数类型也相同却又不同返回类型的方法。

默认字段的初始化

如果在构造器中没有显式地为字段设置初始值,那么就会自动地赋值为默认值:数值为0,布尔值为false,对象引用为null。

这是字段和局部变量的一个重要区别。方法中的局部变量必须明确地初始化。但是在类中,如果没有初始化类中的字段,将会自动地初始化为默认值。

初始化块

public class Data {
    public static int id = 1;

    {
        System.out.println("init block");
    }

    static  {
        System.out.println("static init block");
    }
}

在一个类的声明中,可以包含任意多个初始化块,只要构造这个类的对象,这些块就会被执行。
静态初始化块执行先与初始化块,且只会被执行一次。

类的导入

一个类可以使用所属包中的所有类,以及其他包中的公共类。

文档注释

public class Doc {

    /**
     * @author zhang
     * @version 1.1
     * @see <a href="https://www.baidu.com">百度</a>
     * {@link Data#hello()}
     * @param name 姓名
     * @return 名字
     */
    public String Help(String name) {
        return name;
    }
}

注视抽取命令
如果是一个包

jacadoc -d docDir nameOfPackage

如果文件在无名的包中

javadoc -d docDir *.java

类设计技巧

  1. 一定要保证数据私有
  2. 一定要对数据进行初始化
  3. 不要在类中使用过多的基本类型
  4. 分解有过多职责的类
  5. 类名与方法名要能够体现它们的职责。

二、继承

有些人认为super和this引用是类似的概念,实际上,这样比较并不太恰当。这是因为super不是一个对象的引用,例如,不能将值super赋值给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

子类构造器

如果子类的构造器没有显式地调用超类的构造器,将自动地调用超类的无参构造器。如果超类没有无参构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,Java编译器就会报一个错误。调用构造器的语句只能作为另一个构造器的第一条语句出现

访问修饰符

  1. 仅对本类可见- private
  2. 对外部类完全可见- public
  3. 对本包和子类可见- protected
  4. 对本包可见-不需要修饰符

Object:所有类的超类

equals方法

Object类中的equals方法用于检测一个对象是否等于零一个对象。Object类中实现的equals方法将确定两个对象的引用是否相等。这是一个合理的默认行为,如果两个对象引用相等,这两个对象肯定就相等。不过经常需要给予状态检测对象的相等性,如果对象又相同的状态,才认为这两个对象是相等的。

hashcode方法

由于hashcode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值由对象的存储地址得出。
如果重新定义了equals方法,就必须为用户可能插入散列表的对象重新定义hashcode方法。

枚举类

枚举的构造器总是私有的,如果声明一个enum构造器为public或protected,会出现语法错误。

三、接口

在Java语言中,接口不是类,而是对希望符合这个接口的类的一组需求。

  • 接口中所有方法都自动是public方法。因此,在接口中声明方法时,不必提供关键字public
  • 接口中所有字段总是public static final

接口与抽象类

Java的设计者选择了不支持多重继承,其主要原因是多重继承会让语言变得非常复杂,或者效率会降低。
实际上接口可以通过多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。

默认方法

public interface InterfaceTest {
    default void print() {
        System.out.println("===");
    }
}

可以为接口方法提供一个默认实现。必须用default修饰符标记这样一个方法。

四、内部类

内部类(inner class)是定义在另一个类中的类。为什么需要内部类呢?主要原因有两个:

  • 内部类可以对同一个包中的其他类隐藏
  • 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据。

内部类的对象会有一个隐式引用,指向实例化这个对象的外部类对象。通过这个指针,它可以访问外部对象的全部状态。编译器会修改所有的内部类构造器,添加一个对应外部类引用的参数。
在Java中,静态内部类没有这个附加的指针,所以Java的静态内部类就相当于C++中的嵌套类。

内部类中声明的所有静态字段都必须是final,并初始化为一个编译时常量。如果这个字段不是一个常量,就可能不唯一。
内部类不能有static方法。Java语言规范对于这个限制没有做任何解释。

匿名内部类-todo

静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类有外部类对象的一个引用。为此可以将内部类声明为static,这样就不会生成那个引用。

五、动态代理

利用代理可以在运行时创建实现了一组给定接口的新类。只有在编译时期无法确定需要实现哪些接口时才有必要使用代理。
假设你想构造一个类的对象,这个类实现了一个或多个接口,但是在编译时你可能并不知道这些接口到底是什么。代理类可以在运行时创建全新的类。这样的代理类能够实现你指定的接口。

创建代理类对象

想要创建一个代理对象,需要使用Proxy类的newProxyInstance方法。这个方法有三个参数:

  • 一个类加载器
  • 一个Class对象数组,每个元素对应需要实现的各个接口
  • 一个调用处理器

代理类的特性

需要记住,代理类是在程序运行过程中动态创建的。然而,一旦被创建,它们就变成了常规类,与虚拟机中的任何其他类没有什么区别。所有的代理类都拓展Proxy类。没有定义代理类的名字,Oracle虚拟机将生成一个以字符串$Proxy开头的类名。
对于一个特定的类加载器和预设的一组接口来说,只能有一个代理类。也就是说如果使用同一个类加载器和接口数组调用两次newProxyInstance方法,将得到同一个类的两个对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值