Java基础学习笔记

Posted bfengj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础学习笔记相关的知识,希望对你有一定的参考价值。

继承

继承的基本思想是,可以基于已有的类创建新的类。继承已存在的类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段,使新类能够适应新的情况。

类、超类和子类

使用extends表示继承。

通过拓展超类定义子类的时候,只需要指出子类与超类的不同之处。

只有父类的方法能访问父类的私有字段,这意味如果子类的方法不能直接访问父类的私有字段,需要使用公共接口,类似getSalary()这样的:

    public double getSalary(){
        return this.bonus+super.getSalary();
    }

这里使用的是super.getSalary(),因为我们希望调用的是超类的getSalary方法,而不是当前类的这个方法。

继承绝对不会删除任何字段或方法。

子类的构造器:

    public Manager(String name, double salary){
        super(name,salary);
        bonus = 0;
    }

super(name,salary);是调用超类Employee中的这个构造器:

    public Employee(String name, double salary){
        this.name = name;
        this.salary = salary;
    }

使用super调用构造器的语句必须是子类构造器的第一条语句。

如果子类的构造器没有显式地调用超类的构造器,讲自动调用超类的无参数构造器。如果超类没有无参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,Java编译器就会报告一个错误。

this:

  • 指示隐式参数的引用。
  • 调用该类的其他构造器。

super:

  • 调用超类的方法。
  • 调用超类的构造器。
    public static void main(String[] var0) {
        var staff = new Employee[3];
        Manager boss = new Manager("feng1",123);
        boss.setBonus(123);
        staff[0] = boss;
        staff[1] = new Employee("feng2",123);
        staff[2] = new Employee("feng3",123);
        for(Employee e:staff){
            System.out.println(e.getName());
            System.out.println(e.getSalary());
        }
    }

尽管将e声明为Employee类型,但实际上e既可以引用Employee类型的对象,也可以引用Manager类型的对象。

虚拟机知道e实际引用的对象类型,因此能够正确地调用相应的方法。

一个对象变量可以指示多种实际类型的现象称为多态。在运行时能够自动地选择适当的方法,称为动态绑定

多态

is-a规则,它指出子类的每个对象也是超类的对象。

is-a规则的另一种表述是替换原则。它指出程序中出现超类对象的任何地方都可以使用子类对象替换。

在Java程序设计语言中,对象变量是多态的。一个Employee类型的变量既可以引用一个Employee类型的对象,也可以引用Employee类的任何一个子类的对象。

不能将超类的引用赋给子类变量。

在Java中,子类引用的数组可以转换成超类引用的数组,而不需要使用强制类型转换。

在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。特别是,如果超类方法是public,子类方法必须也要声明为public。

阻止继承:final类和方法

不允许拓展的类被称为final类。在定义类的时候使用final修饰符就表明这个类是final类。

类中的某个特定方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法(final类中的所有方法自动地成为final方法,但是不包括字段,也就是说字段不会自动成为final)。

强制类型转换

将一个值存入变量时,编译器将检查你是否承诺过多。如果将一个子类的引用赋值给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量时,就承诺过多了。必须进行强制类型转换,这样才能够通过运行时的检查。

        Manager test1 = new Manager("test1",111);
        Employee test2 = new Employee("test2",111);
        Employee test3 = test1;  //可以
        Manager test4 = (Manager) test2;  //必须强制类型转换。而且这样是有问题的。

我个人的理解就是,子类可以直接隐式转换成父类,但是父类需要强制类型转换成子类。

在进行强制类型转换之前,先查看是否能够成功地转换。为此只需要使用instanceof操作符就可以实现。

instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。

  • instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。
  • instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误。
        Manager test1 = new Manager("test1",111);
        Employee test2 = new Employee("test2",111);
        Employee test3 = test1;
        //Manager test4 = (Manager) test2;
        if (test2 instanceof Manager){
            Manager test4 = (Manager) test2;
        }

所以很明显这是进入不了if的。

  • 只能在继承层次内进行强制类型转换。
  • 在将超类强制转换成子类之前,应该使用instanceof进行检查

一般情况下,最好尽量少用强制类型转换和instanceof运算符

抽象类

为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。

public abstract class Person 
{
    public abstract String getDescription();
}

除了抽象方法之外,抽象类还可以包含字段和具体方法。

public abstract class Person
{
    private String name;

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

    public String getName(){
        return this.name;
    }
}

有些程序员认为,在抽象类中不能包含具体方法,。建议尽量将通用的字段和方法(不管是否是抽象的)放在超类(不管是否是抽象类)中。

拓展抽象类可以有两种选择,一种是在子类中保留抽象类的部分或所有抽象方法仍未定义,这样就必须将子类也标记为抽象类;另一种做法是定义全部方法,这样一来,子类就不是抽象的了。

即使不含抽象方法,也可以将类声明为抽象类。

**抽象类不能实例化。**但是可以定义一个抽象类的成员变量,但是这样一个变量只能引用非抽象子类的对象。

Person person = new Student("feng","aaa");

受保护访问

在Java中,保护字段只能由同一个包中的类访问。

  1. 仅对本类可见:private
  2. 对外部完全可见:public
  3. 对本包和所有子类可见:protected
  4. 对本包可见:默认(很遗憾),不需要修饰符

Object:所有类的超类

可以使用Object类型的变量引用任何类型的对象:

Object obj = new Manager("feng",123);

Object类型的变量只能用于作为各种值得一个泛型容器。要想对其中得内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的强制类型转换;

        Object obj = new Manager("feng",123);
        Manager feng = (Manager) obj;
        System.out.println(feng.getName());

在Java中,只有基本类型不是对象,例如数值、字符、布尔类型等都不是对象。

equals方法

Object类中的equals方法用于检测一个对象是否等于另外一个对象。

例如Employee类实现一个equals方法:

    public boolean equals(Object otherObject)
    {
        if(this == otherObject) return true;

        if(otherObject == null) return false;

        if(getClass() != otherObject.getClass()) return false;

        Employee other = (Employee) otherObject;

        String name = getName();
        return Objects.equals(getName(),other.getName())
                &&salary == other.getSalary()
                &&getId()==other.getId();
    }

getClass方法将返回一个对象所属的类。

Returns the runtime class of this Object. The returned Class object is the object that is locked by static synchronized methods of the represented class.

为了防备name可能为null的情况,需要使用Objects.equals方法。如果两个参数都为null,则Objects.equals(a,b)调用将返回true。如果其中一个参数为null,则返回false;否则,如果两个参数都不为null,则调用a.equals(b)

完美的equals方法的建议:

  1. 显式参数命名为otherObject,稍后需要将它强制转换为另一个名为other的变量。
  2. 检测this与otherObject是否相等:
    if(this == otherObject) return true;
  3. 检测otherObject是否为null,如果为null,返回false。这项检测是很必要的。
    if(otherObject == null) return false;
  4. 比较this与otherObject的类。如果equals的语义可以在子类中改变,那就使用getClass检测:
    if(getClass() != otherObject.getClass()) return false;
    如果所有的子类都有相同的相等性语义,可以使用instanceof检测:
    if(!(otherObject instanceof ClassName)) return false;
  5. otherObject强制转换为相应类类型的变量:
    ClassName other = (ClassName) otherObject;
  6. 根据相等性概念的要求来比较字段。使用==比较基本类型字段,使用Objects.equals比较对象字段。如果所有的字段都匹配,就返回true;否则返回false。
    如果在子类中重新定义equals,就要在其中包含一个super.equals(other)的调用。

对于数组类型的字段,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。

可以使用@Override标记要覆盖超类方法的那些子类方法:

    @Override
    public boolean equals(Object otherObject)

如果出现了错误,并且正在定义一个新方法,编译器就会报告一个错误。

例如这样,并没有覆盖Object类的equals方法,因此会报告错误:

    @Override
    public boolean equals(Employee otherObject)

toString方法

返回表示对象值的一个字符串。

只要对象与一个字符串通过操作符 “+”连接起来,Java编译器就会自动地调用toString方法来获得这个对象的字符串描述。

可以不写为x.toString(),而写作""+x。与toString不同的是,即使x是基本类型,这条语句照样能够执行。

打印数组利用Arrays.toString

        double[] x = {1,2,3,4,5};
        System.out.println(Arrays.toString(x));

泛型数组列表

ArrayList是一个有类型参数的泛型类。为了指定数组列表保存的元素对象的类型,需要用一对尖括号将类名括起来追加到ArrayList后面,例如ArrayList<Integer>

        ArrayList<Employee> staff1 = new ArrayList<Employee>();
        ArrayList<Employee> staff2 = new ArrayList<>();
        var staff3 = new ArrayList<Employee>();

最好使用var关键字以避免重复写类名;如果没有使用var关键字,可以省去右边的类型参数。(菱形语法)

当然还可以把初始容量传递给ArrayList构造器

        ArrayList<Employee> staff1 = new ArrayList<Employee>(100);

数组列表的容量与数组的大小有一个非常重要的区别。如果分配一个有100个元素的数组,数组就有100个空位置可以使用。而容量为100个元素的数组列表只是可能保存100个元素。但是在最初,甚至完成初始化构造之后,数组列表不包含任何元素。

使用add方法可以将元素添加到数组列表中。如果需要在数组列表的中间插入元素,可以使用add方法并提供一个索引参数。

要设置第i个元素,可以使用set

要得到数组列表的元素,可以使用get

        ArrayList<Employee> staff = new ArrayList<>();
        staff.add(new Employee("feng",123));
        System.out.println(staff.get(0).getName()); //feng
        staff.set(0,new Employee("hhh",3456));
        System.out.println(staff.get(0).getName()); //hhh

只有当数组列表的大小大于i时,才能够调用list.set(i,x)。set方法只是用来替换数组中已经加入的元素。

size方法将返回数组列表中包含的实际元素个数

        ArrayList<Employee> staff = new ArrayList<>(100);
        System.out.println(staff.size()); //0
        staff.add(new Employee("feng",123));
        System.out.println(staff.size()); //1

remove方法可以从数组列表中间删除一个元素:

        ArrayList<Employee> staff = new ArrayList<>(100);
        System.out.println(staff.size()); //0
        staff.add(new Employee("feng",123));
        System.out.println(staff.size()); //1
        Employee e = staff.remove(0);
        System.out.println(staff.size()); //0
        System.out.println(e.getName());  //feng

可以使用for each循环遍历数组列表的内容:

        ArrayList<Employee> staff = new ArrayList<>();
        staff.add(new Employee("feng1",123));
        staff.add(new Employee("feng2",123));
        staff.add(new Employee("feng3",123));
        staff.add(new Employee("feng4",123));
        for(Employee e:staff){
            System.out.println(e.getName());
        }

对象包装器与自动装箱

所有的基本类型都有一个与之对应的类。这些类称为包装器。

Integer,Long,Float,Double,Short,Byte,Character,Boolean (前六个类派生于公共的超类Number)

包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类型还是final,因此不能派生它们的子类。

对于ArrayList,尖括号中的类型不允许是基本类型,因此要用到包装器类,例如Integer等。

var list = new ArrayList<Integet>();

对于这种调用

        var list =new ArrayList<Integer>();
        list.add(2);
        System.out.println(list.get(0));

将自动变成:

        var list =new ArrayList<Integer>();
        list.add(Integer.valueOf(2));
        System.out.println(list.get(0));

这种变换称为自动装箱。

相反的,把Integet赋给一个int时,会自动拆箱:

int n = list.get(0);
System.out.println(n);

装箱和拆箱是编译器要做的工作,而不是虚拟机。编译器在生成类的字节码时会插入必要的方法调用。虚拟机只是执行这些字节码。

参数数量可变的方法

可以提供参数数量可变的方法(有时这些方法被称为"变参"(varargs)方法)。

这里的省略号...是Java代码的一部分,它表明这个方法可以接收任意数量的对象。例如:

    public static void main(String[] args) {
        printNumber(123,456);
    }

    public static void printNumber(double... numbers){
        for(double e:numbers){
            System.out.println(e);
        }
    }

允许将数组作为最后一个参数传递给可变参数的方法。

因此,如果一个已有方法的最后一个参数是数组,可以把它重新定义为有可变参数的方法,而不会破坏任何已有的代码。

枚举类

定义枚举类型:

    public enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE};

实际上,这个声明定义的类型是一个类,它刚好有四个实例,不可能构造新的对象。

因此,在比较两个枚举类型的值时,并不需要调用equals,直接使用==就可以了。

同样可以为枚举类型增加构造器、方法和字段。

enum Size
{
    SMALL("S"),MEDIUM("M"),LARGE("L"),EXTRA_LARGE("XL");

    private String abbreviation;
    private Size(String abbreviation)
    {
        this.abbreviation = abbreviation;
    }
    public String getAbbreviation()
    {
        return this.abbreviation;
    }
}

枚举的构造器总是私有的。

所有的枚举类型都是Enum类的子类。

System.out.println(Size.SMALL.getClass().getName());//com.javalearn.Size

它们继承了这个类的许多方法,其中最有用的一个是toString,这个方法返回枚举常量名:

System.out.println(Size.SMALL.toString());  //SMALL
System.out.println(Size.SMALL.toString().getClass().getName());  //java.lang.String

toString的逆方法是静态valueOf

System.out.println(Enum.valueOf(Size.class,"SMALL"));//SMALL
System.out.println(Enum.valueOf(Size.class,"SMALL").getClass().getName());//com.javalearn.Size

每个枚举类型都有一个静态的values方法,它将返回一个包含全部枚举值的数组。

        Size[] values = Size.values();
        for(Size e:values){
            System.out.println(e);
        }
/*
SMALL
MEDIUM
LARGE
EXTRA_LARGE
*/

ordinal方法返回enum声明中枚举常量的位置,位置从0开始计数。

System.out.println(Size.SMALL.ordinal()); //0

反射

(这部分只是跟着书上的反射知识过了一遍,之后会再专门针对Java安全中反射的利用进行一下学习)

反射库提供了一个丰富且精巧的工具集,可以用来编写能够动态操纵Java代码的程序。

能够分析类能力的程序称为反射

反射机制可以用来:

  • 在运行时分析类的能力。
  • 在运行时检查对象。
  • 实现泛型数组操作代码。
  • 利用Method对象,这个对象很像C++中的函数指针。

Class类

在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确方法。

可以使用一个特殊的Java类访问这些信息。保存这些信息的类名为Class

Object类中的getClass()方法将会返回一个Class类型的实例。

        var e = new Employee("feng",123);
        Class cl = e.getClass();
        System.out.println(cl); //class com.javalearn.Employee

Class对象会描述一个特定类的属性。最常用的Class方法就是getName。这个方法将返回类的名字。

        var e = new Employee("feng",123);
        Class cl = e.getClass();
        System.out.println(cl.getName());

如果类在一个包里,包的名字也作为类名的一部分。所以我这里会返回com.javalearn.Employee

还可以使用静态方法forName获得类名对应的Class对象。

        String className = "com.javalearn.Employee";
        Class cl = Class.forName(className);

获得Class类对象的第三种方法是一个很方便的快捷方式。如果T是任意的Java类型(或void关键字),T.class将代表匹配的类对象。

Class

以上是关于Java基础学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

C#学习笔记——需要注意的基础知识

学习笔记:python3,代码片段(2017)

unity shader学习笔记 shader基础结构以及Properties面板

JSP 学习笔记

每日学Java系列-Java零基础学习笔记开发第一个Java程序:HelloWorld

java语言基础,学习笔记