JAVA基础

java基础

方法 - 廖雪峰的官方网站 (liaoxuefeng.com)

1. Java的基础结构

public class Hello {
 public static void main(String[] args) {
     // 向屏幕输出文本:
     System.out.println("Hello, world!");
 }
} // class定义结束
  • 基础构成单位是class,是一个关键字,此时定义类名为hello
    • 类名最好用大写字母开头,以字母数字下划线组成,形如HelloWorld
    • public是访问修饰符,表示该class是公开的,若无则不能从命令行执行
  • class内部,可以定义若干方法(method)
    • 方法名最好用小写字母开头,以字母数字下划线组成,形如helloWorld
    • 这里的方法名是main,返回值是void,表示没有任何返回值。
    • 我们注意到public除了可以修饰class外,也可以修饰方法。而关键字static是另一个修饰符,它表示静态方法,后面我们会讲解方法的类型
  • 注意:
    • 目前,我们只需要知道,Java入口程序规定的方法必须是静态方法,方法名必须为main,括号内的参数必须是String数组(psvm)
    • Java的每一行语句必须以分号结束

2. 变量和数据类型

基本的变量类型

  • 整数类型:byteshortintlong
  • 浮点数类型:floatdouble
  • 字符类型:char
  • 布尔类型:boolean

Java基本数据类型占用的字节数:
在这里插入图片描述

注:

  • 变量如果再该作用域中声明了类型,等号是赋值语句,下次赋值时无需重新声明类型
  • long型的结尾需要加L;对于float类型,需要加上f后缀;字符类型char(单引号)表示一个字符。Java的char类型除了可表示标准的ASCII外,还可以表示一个Unicode字符:
  • 同一个数的不同进制的表示是完全相同的,例如15=0xf0b1111

var关键字

类型的名字太长时,可以使用var关键字,编译器会根据赋值语句自动推断出变量。

​ 原始

StringBuilder sb = new StringBuilder();

​ 改(少写了变量类型)

var sb = new StringBuilder();

变量的作用范围

​ 在语句块中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。超出了作用域引用这些变量,编译器会报错。

{
 ...
 int i = 0; // 变量i从这里开始定义
 ...
 {
     ...
     int x = 1; // 变量x从这里开始定义
     ...
     {
         ...
         String s = "hello"; // 变量s从这里开始定义
         ...
     } // 变量s作用域到此结束
     ...
     // 注意,这是一个新的变量s,它和上面的变量同名,
     // 但是因为作用域不同,它们是两个不同的变量:
     String s = "hi";
     ...
 } // 变量x和s作用域到此结束
 ...
} // 变量i作用域到此结束

即:作用范围在那个括号{}里,就是从它定义开始到包含他的这一层结束

3.布尔运算

对于布尔类型boolean,永远只有truefalse两个值。

​ 布尔运算是一种关系运算,包括以下几类:

  • 比较运算符:>>=<<===!=
  • 与运算 &&
  • 或运算 ||
  • 非运算 !

4.字符和字符串

​ 一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节。要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可

int n1 = 'A'; // 字母“A”的Unicodde编码是65
int n2 = '中'; // 汉字“中”的Unicode编码是20013

常见的转义字符包括:

  • \" 表示字符"
  • \' 表示字符'
  • \\ 表示字符\
  • \n 表示换行符
  • \r 表示回车符
  • \t 表示Tab
  • \u#### 表示一个Unicode编码的字符

5.数组类型

假如有一组类型相同的变量,可以通过以下方法定义

public class Main {
 public static void main(String[] args) {
     // 2个int整数:
     int[] num = new int[];
     num[0] = 68;
     num[1] = 79;
 }
}

注:

  • 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false
  • 数组一旦创建后,大小就不可改变,数组索引从0开始。
  • 可以通过索引访问数组元素,但索引超出范围将报错;
  • 可以用数组变量.length获取数组大小

​ 这里注意到原来names[1]指向的字符串"XYZ"并没有改变,仅仅是将names[1]的引用从指向"XYZ"改成了指向"cat",其结果是字符串"XYZ"再也无法通过names[1]访问到了。

在这里插入图片描述

面向对象

1.方法

权限修饰符

直接操作field,容易造成逻辑混乱。为了避免外部代码直接去访问field,我们可以用private修饰field,拒绝外部访问

public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setBirth(2008);
        System.out.println(ming.getAge());
    }
}

class Person {
    private String name;
    private int birth;

    public void setBirth(int birth) {
        this.birth = birth;
    }

    public int getAge() {
        return calcAge(2019); // 调用private方法
    }

    // private方法:
    private int calcAge(int currentYear) {
        return currentYear - this.birth;
    }
}

this变量

在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。

如果没有命名冲突,可以省略this。例如:

class Person {
    private String name;

    public String getName() {
        return name; // 相当于this.name
    }
}

但是,如果有局部变量和字段重名,那么局部变量优先级更高,就必须加上this

class Person {
    private String name;
    public void setName(String name) {
        this.name = name; // 前面的this不可少,少了就变成局部变量name了
    }
}
可变参数(类型…名称)
class Group {
    private String[] names;

    public void setNames(String... names) {
        this.names = names;
    }
}
参数绑定

调用方把参数传递给实例方法时,调用时传递的值会按参数位置一一绑定。

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        int n = 15; // n的值为15
        p.setAge(n); // 传入n的值
        System.out.println(p.getAge()); // 15
        n = 20; // n的值改为20
        System.out.println(p.getAge()); // 15还是20?,答案是15
    }
}

class Person {
    private int age;
    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

原因是setAge()方法获得的参数,复制了n的值,传进来的是15而不是n,p.age和局部变量n互不影响。

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        String[] fullname = new String[] { "Homer", "Simpson" };
        p.setName(fullname); // 传入fullname数组
        System.out.println(p.getName()); // "Homer Simpson"
        fullname[0] = "Bart"; // fullname数组的第一个元素修改为"Bart"
        System.out.println(p.getName()); // "Homer Simpson"还是"Bart Simpson"?答案是Bart Simpson
    }
}

class Person {
    private String[] name;

    public String getName() {
        return this.name[0] + " " + this.name[1];
    }

    public void setName(String[] name) {
        this.name = name;
    }
}

​ 注意到setName()的参数现在是一个数组。一开始,把fullname数组传进去,然后,修改fullname数组的内容,结果发现,实例p的字段p.name也被修改了!

​ 引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。

2.构造方法

默认构造方法
class Person {
    public Person() {
    }
}

如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法

public class Main {
    public static void main(String[] args) {
//      Person p = new Person(); // 编译错误:找不到这个构造方法
        Person p = new Person("Xiao Ming", 15);
        System.out.println(p.getName());
        System.out.println(p.getAge());
        
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}
多构造方法
class Person {
    private String name;
    private int age;

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

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

    public Person() {
    }
}
方法重载
class Hello {
    public void hello() {
        System.out.println("Hello, world!");
    }

    public void hello(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public void hello(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, " + name + "!");
        } else {
            System.out.println("Hello, " + name + "!");
        }
    }
}

3.继承

Java使用extends关键字来实现继承:

class Person {
    private String name;
    private int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // 编译错误:无法访问name字段
    }
}

Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。

如果我们定义一个继承自PersonTeacher,它们的继承树关系如下:

       ┌───────────┐
       │  Object   │
       └───────────┘
             ▲
             │
       ┌───────────┐
       │  Person   │
       └───────────┘
          ▲     ▲
          │     │
          │     │
┌───────────┐ ┌───────────┐
│  Student  │ │  Teacher  │
└───────────┘ └───────────┘

protected

​ 继承有个特点,就是子类无法访问父类的private字段或者private方法。我们需要把private改为protected。用protected修饰的字段可以被子类访问:

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

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // OK!
    }
}

super

super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName。例如:

class Student extends Person {
    public String hello() {
        return "Hello, " + super.name;
    }
}

​ 实际上,这里使用super.name,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段。

public class Main {
    public static void main(String[] args) {
        Student s = new Student("Xiao Ming", 12, 89);
    }
}

class Person {
    protected String name;
    protected int age;

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

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age); // 调用父类的构造方法Person(String, int)
        this.score = score;
    }
}

注: 如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。

如果直接像下边这样调用

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
//super(); // 自动调用父类的构造方法,假如此时并没有无参数的构造方法,就会报错
        this.score = score;
    }
}

阻止继承

正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。

从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。

public sealed class Shape permits Rect, Circle, Triangle {
    ...
}
public final class Rect extends Shape {...}

上述Shape类就是一个sealed类,它只允许指定的3个类继承它。限定了可以继承的对象.

向上转型

如果Student是从Person继承下来的,那么,一个引用类型为Person的变量,能否指向Student类型的实例?

Person p = new Student(); //这种指向是允许的

这是因为Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!

这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。

向上转型实际上是把一个子类型安全地变为更加抽象的父类型:

Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok

注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object

向下转型

和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。

错误示例:

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

​ 把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。报错ClassCastException

为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:

Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false

Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true

Student n = null;
System.out.println(n instanceof Student); // false

instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false

利用instanceof,在向下转型前可以先判断:

Person p = new Student();
if (p instanceof Student) {
    // 只有判断成功才会向下转型:
    Student s = (Student) p; // 一定会成功
}

从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。例如,对于以下代码:

Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

可以改写如下:

// instanceof variable:
public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // 可以直接使用变量s:
            System.out.println(s.toUpperCase());
        }
    }
}

区分继承和组合

在使用继承时,我们要注意逻辑一致性。

考察下面的Book类:

class Book {
    protected String name;
    public String getName() {...}
    public void setName(String name) {...}
}

Student可以持有一个Book,具有has关系不应该使用继承,而是使用组合,即

class Student extends Person {
    protected Book book;
    protected int score;
}

因此,继承是is关系,组合是has关系。

4.多态

Override

在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。

例如,在Person类中,我们定义了run()方法:

class Person {
    public void run() {
        System.out.println("Person.run");
    }
}

在子类Student中,覆写这个run()方法:

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override

注意:方法名相同,方法参数相同,但方法返回值不同,也是不同的方法。在Java程序中,出现这种情况,编译器会报错。

但是@Override不是必需的。

在上一节中,我们已经知道,引用变量的声明类型可能与其实际类型不符,例如:

Person p = new Student();
public class Main {
    public static void main(String[] args) {
        // 给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税:
        Income[] incomes = new Income[] {
            new Income(3000),
            new Salary(7500),
            new StateCouncilSpecialAllowance(15000)
        };
        System.out.println(totalTax(incomes));
    }

    public static double totalTax(Income... incomes) {
        double total = 0;
        for (Income income: incomes) {
            total = total + income.getTax();
        }
        return total;
    }
}

class Income {
    protected double income;

    public Income(double income) {
        this.income = income;
    }

    public double getTax() {
        return income * 0.1; // 税率10%
    }
}

class Salary extends Income {
    public Salary(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        if (income <= 5000) {
            return 0;
        }
        return (income - 5000) * 0.2;
    }
}

class StateCouncilSpecialAllowance extends Income {
    public StateCouncilSpecialAllowance(double income) {
        super(income);
    }

    @Override
    public double getTax() {
        return 0;
    }
}

可见,多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

覆写Object方法

因为所有的class最终都继承自Object,而Object定义了几个重要的方法:

  • toString():把instance输出为String
  • equals():判断两个instance是否逻辑相等;
  • hashCode():计算一个instance的哈希值。

在必要的情况下,我们可以覆写Object的这几个方法。例如:

class Person {
    ...
    // 显示更有意义的字符串:
    @Override
    public String toString() {
        return "Person:name=" + name;
    }

    // 比较是否相等:
    @Override
    public boolean equals(Object o) {
        // 当且仅当o为Person类型:
        if (o instanceof Person) {
            Person p = (Person) o;
            // 并且name字段相同时,返回true:
            return this.name.equals(p.name);
        }
        return false;
    }

    // 计算hash:
    @Override
    public int hashCode() {
        return this.name.hashCode();
    }
}

调用super

在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。例如:

class Person {
    protected String name;
    public String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    @Override
    public String hello() {
        // 调用父类的hello()方法:
        return super.hello() + "!";
    }
}

final

继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override

class Person {
    protected String name;
    public final String hello() {
        return "Hello, " + name;
    }
}

Student extends Person {
    // compile error: 不允许覆写
    @Override
    public String hello() {
    }
}
  • 28
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值