定义Java类:

public class 类名{};

我们使用的Main其实也是一个类

之后在大括号里面可以定义该类的各种属性,例如:

public class Person
{
	String name;   //String大写
	int age;
}    //注意结尾没有分号

使用类

创建

使用new来创建类的对象,类似于C++中为指针分配空间

例如:

Person p=new Person();

此时p存放的就不是对象本身了,而是对象的引用

也可以创建一个空对象:

Person p=null;

打印

对于类的对象来说,println会自动调用toString方法,不用显式地写出,不过默认toString会以 类名+@+对象内存地址的十六进制表示出来,想要正常表示需要重写toString函数

访问和改变属性

使用 “ . “ 来访问对象的属性,并且做出修改。

例如:

p.name="别无选择“;
p.age=18;

注:各种类型也是有初始值的,int等类型默认为0,bool默认为false,类类型默认为null

创建方法

对于每一个类,可以在其中定义不同的方法,即函数

格式和函数定义一样

例如:

public class Person
{
    String name;
    int age;
    void greet{
        System.out.println("你好");
    }
}
Person p=new Person();
p.name="别无选择";
p.age=18;
p.greet();

使用方法时,格式为:对象.方法名();

this

Java类中也有this关键字,不过和C不同,此时this不是指针,使用时也不用“ →”来访问属性,而是使用” . “

例如:

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

重载

Java类中也存在函数的重载,语法和C相同

区别:重写

添加代码块

在类定义中添加一个代码块,此时块中的代码会在构造对象时执行一次

public class Person{
    String name;
    int age;
    {
        System.out.println("我是代码块“);
    }
}

构造方法

每个类在定义时都隐含着一个默认的构造方法(构造器),其形式为:

Person(){};

无返回值,函数名与类名相同

可以自己显式地定义构造方法,此时默认构造方法会被覆盖掉。

注:代码块和构造方法的执行顺序:先代码块,再构造方法

静态变量,静态方法,静态代码块

静态变量

类中也可以定义静态变量,形式为:

static String info;

此时类中所有对象共用该’变量,并且可以用类名之间访问静态变量

System.out.println(Person.info);

何时对静态变量进行初始化?

JVM只在类使用的时候才加载类,以下是加载类的情况:

  • 访问类的静态变量,或者为静态变量赋值
  • new创建类的实例(隐式加载)
  • 调用类的静态方法
  • 子类初始化时
  • 其他

所有被标记为静态的内容,会在类刚加载的时候就分配,而不是在对象创建的时候分配,所以说静态内容一定会在第一个对象初始化之前完成加载

静态方法

和静态变量类似,用static修饰以定义静态方法

static void test(){}

此时静态方法中只能访问静态变量和其他的静态方法,不能使用this , super关键字

静态代码块

static{}

执行顺序

在创建一个类的对象时,先进行静态变量初始化,再执行静态代码块,再进行成员变量初始化,再执行普通代码块,再进行构造方法

访问权限控制

类和类的属性都有其访问权限,分为:默认,public,protected,private

访问权限可以是默认public

例如:

public class Person(){}
class Person(){}

类的属性(变量和方法)

四种访问权限均可以

  • 默认:只能被类本身和同包中的其他类访问。
  • public:标记为公共的内容,允许在任何地方被访问。
  • protected:标记为受保护的内容可以能被类本身和同包中的其他类访问,也可以被子类访问(继承)。
  • private:标记为私有的内容无法被除当前类以外的任何位置访问。

注:访问权限的关键字均写在类/类属性的前面

类的封装

将类的变量都设置为private,通过getter和setter函数实现类的变量的访问和改变

也可以对类的构造方法做出改变:

public class Person{
	private String name;
	int age;
	public void getName{ return this.name;}
	public void setName(String name){
		this.name=name;
	}
	
	//设置默认构造方法为私有
	private Person(){}
	public static void getInstance(){
		return new Person();
	}

通过这种方式可以实现单例(单例模式就是全局只能使用这一个对象,不能创建更多的对象)

public class Person{
	//定义私有静态变量,此时insstance即为只能使用的对象
	private static Person instance;
	
	private Person(){}
	public static void getInstance(){
		if(instance==nulll)
			instance=new Person();
		return instance;
	}
}

类的继承(无多继承)

Java中类的继承要使用extends关键字

public class Worker extends Person{}

此时,Worker类为Person类的子类,Person类为Worker类的父类(超类)

子类继承了父类的所有属性,只要父类属性未用private修饰,那么子类都可以访问

注:类的继承可以不断向下,但是同时只能继承一个类,同时,标记为final的类不允许被继承

public final class Person {  //class前面添加final关键字表示这个类已经是最终形态,不能继承
  
}

在继承中,每个类中都有一个虚方法表,当子类中调用某个方法时,会先从虚方法表中寻找,这样提高了效率,其中未用 private static final 修饰的方法均会添加到虚方法表中

继承中构造方法的访问特点

  • 父类中的构造方法不会被子类继承
  • 子类中所有的构造方法默认先访问父类中的无参构造,再访问自己

为什么?

子类初始化时,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。因此子类构造方法第一句默认都是 super() 来调用父类的无参构造方法

特殊情况

父类中的构造方法含参(不是默认)

public class Person{
	String name;                //可以用protected修饰变量,此时外部就不能访问这些变量了
	int age;
	String profession;
	Person(String name,int age){
		this.name=name;
		this.age=age;
		this.profession=profession;
	}
	public void hello(){
		System.out.println("你好!我叫"+name+"今年"+age+"岁");
	}
}

此时子类必须要在构造方法中调用:

public class Worker extends Person{
	public Worker(String name,int age){
		**super(name,age,"工人")**;    //super代表父类,父类的构造方法就是super()
	}
}

注:super函数前不能有任何语句。

为什么?

因为子类在构造时,不仅要初始化子类的属性,还需要初始化父类的属性,所以说在默认情况下,子类其实是调用了父类的构造方法的,只是在无参的情况下可以省略,但是现在父类构造方法需要参数,那么我们就需要手动指定了。

继承存在向上转型,即可以用子类当做父类:

Person person=new Student("别无选择","18");
person.hello();

当然,此时也可以通过强制类型转换再将person转换为Student类(前提是它原本new的时候就为Student)[将一个被当做父类使用的子类对象,转换回子类]

Student student=(Student)person;

关键字instanceof

可以用来判断某个变量所引用的对象是否为某个类或者某个类的子类

public static void main(String[] args) {
    Person person = new Student("小明", 18);
    if(person instanceof Student) {   //我们可以使用instanceof关键字来对类型进行判断
        System.out.println("对象是 Student 类型的");
        Student student = (Student) person;//先强转类型再使用方法
        student.study();
    }
    if(person instanceof Person) {
        System.out.println("对象是 Person 类型的");
    }
}

在使用instanceof时,如果要使用对应类的方法必须先强制转换类型

但是在Java16中提供了一种新写法**(类型判断模式匹配)**:

Person person = new Student("小明", 18);
    if(person instanceof Student student) {   //这里的student即为强转后的变量名
        System.out.println("对象是 Student 类型的");
        student.study();
    }

同名

子类可以定义与父类相同名称的变量,此时默认指向为子类中定义的变量

如果想要访问父类中该变量,可以使用super关键字:

public class Person{
	String name="别无选择";                
	int age=18;
}	
public class Worker extends Person{
	String name;
	void work(){
		System.out.println("我是"+name+",我在工作!");
}
	Worker worker=new Worker();
	worker.work();

此时name将打印为”别无选择”

如果改为 super.name() ,将打印为”null” (super只能往上跳一级,不能super.super)

方法的重写

重写不同于重载,重载是为一个方法提供了多种形式,而重写则是将一个新的同名方法覆盖了原方法

条件

  • 在开头加一个注释 @Override,这样编译器可以检查重写形式的正确性(不加也行)
  • 重写方法的名称,形参列表必须与父类中保持一致
  • 子类重写父类方法时,访问权限 子类必须大于父类(默认< protected < public)
  • 子类重写父类方法时,返回值类型子类必须小于等于父类
  • 建议:重写的方法尽量和父类保持一致
  • 只有被添加到虚方法表中的方法才能被重写

例如,对Object中的equals方法进行重写,使其在不同对象只要变量值相同下就返回true:

@Override
public boolean equals(Object obj) {
      if(obj==null)return false;
      if(obj instanceof Person){
          Person person=(Person) obj;
          return this.name.equals(person.name) &&
                  this.age==person.age &&
                  this.sex.equals(person.sex);
      }
      return false;
  } 

注:此时对于Object定义的变量,但指向对象是Person来说,使用equals方法仍然对应的是重写之后的equals方法( Object object=new Person(); )

注意

子类重写的方法内部其实也可以调用原本父类的方法(仅限类的定义中使用),此时要用到super关键字。

在使用之后执行时,程序不仅会执行原本父类的方法,也会接着执行子类的方法。

public class Person{
	public void test(){System.out.println("原本的方法");
}
public class Student extends Person{
	public void test(){
		super.test();
		System.out.println("重写后的方法");
	}
}

类的多态

多态就是同类型的对象,表现出了不同的形态

表现形式:父类类型 对象名称 = 子类对象;

前提

  • 有继承关系
  • 有父类引用指向子类对象( Fu f = new Zi()
  • 有方法重写

调用

成员变量

编译看左边,运行也看左边

编译时判断父类中是否有该成员变量,运行时打印的也是该成员变量

解释:继承时子类会把父类的成员变量都继承下来

成员方法

编译看左边,运行看右边

编译时判断父类中是否有该成员方法,运行时的是子类中的成员方法

解释:如果子类对方法进行了重写,那么虚方法表中是会把父类的方法进行覆盖的

优劣

优势

方法中,如果使用父类类型作为参数,就可以接收所有的子类对象

缺点

不能调用子类中的特有方法

解决方案:再将父类对象强转为子类类型的对象,但是不能瞎转,如果转换为其他类型的子类就会报错,可以使用 instance of 先判断这个对象是否属于某个子类,再进行强制类型转换

补充: instance of 新特性
可以直接通过一行代码进行判断类型 + 强转

Person p = new Student();
if(p instanceof Student s){
//直接判断p是否属于Student类型,如果属于就强转为Student
	p.study();
} else if(p instanceof Worker w){
	p.work();
}

类中的final

类中的final也是修饰在各种变量的前面的(但要放在访问权限后面,例如 public final void test(){}

对于变量,加上final后,该变量在运行中只能被赋值一次

  • 基本数据类型:记录的值不能发生改变
  • 引用数据类型:记录的地址值不能发生改变,内部的属性值还是可以改变的

对于方法,加上final后,代表该方法处于最终形态,不能再被重写(能被重载)

对于类,表面该类是最终类,不能被继承

抽象类

定义类时,在访问权限后加 abstract 关键字,可使该类变为抽象类

抽象类中可以定义抽象方法(并不是一定有抽象方法),形式与定义抽象类相同,函数体则是不用给出,此时子类中**必须重写(实现)**该抽象方法

public abstract class Person{
    String name;
    int age;
    String sex;
    
    public abstract void exam();
}

public class Student extends Person{
    public void exam(){
        System.out.println("会考试");
    }
}

此时就无法使用new来创建Person类了,因为Person类为抽象类,而抽象类中可能会存在某些方法没有实现。只能创建它的子类

(当然,也可以用匿名内部类直接new创建Person类)

因此,抽象类主要用作继承,而且抽象类的子类也可以是抽象类

注:抽象方法的访问权限不能是private

总结:

  • 抽象类不能实例化
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  • 可以有构造方法(在创建子类时对对象进行赋值)
  • 抽象类的子类
    • 要么重写抽象类中的所有抽象方法
    • 要么是抽象类

基本类型包装类

Java通过把基本类型(byte,short,int,float,double,char,boolean)各自封装为类,实现面向对象的功能

其对应如下:

  • byte → Byte
  • short → Short
  • int → Integer
  • long → Long
  • float → Float
  • double→ Double
  • char → Charater
  • boolean→ Boolean

使用

创建

  1. 利用构造方法(Java5以前)

这里以Integer为例:

Integer i = new Integer(10);

注意,定义后,引用所指对象的值(这里是10)不能改变,它是 final 类型的(即使令i = i+10,此时引用所指的是一个新的对象)

  1. 利用静态方法 valueOf (Java5以前)
Integer i1 = Integer.valueOf(123);
Integer i2 = Integer.valueOf("123");
Integer i3 = Integer.valueOf("123",8);//8进制

自动装箱拆箱(Java5)

其实,包装类型支持自动装箱,即可以直接将一个对应的基本类型值作为对应包装类型引用变量的值:

Integer i = 10;  //Integer i = Integer.valueOf(10)

同时也支持拆箱:

Integer i = 10;  
int a = i;     //int a= i.intValue();

这样,包装类就能轻松地参与到基本类型的运算中

比较

值得注意的是,通过之前学习可知,即使引用所指类的成员变量相同,只要它们指的是不同对象, == 返回的就是false。对于自动装箱来说,这个结论不一定成立

比如:

Integer a = 10,b = 10;
sout(a==b);

返回的是 true

Integer a = 128,b = 128;
sout(a==b);

返回的是 false

这是因为:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)   //这里会有一个IntegerCache,如果在范围内,那么会直接返回已经提前创建好的对象
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

IntegerCache会默认缓存-128127之间的所有值,将这些值提前做成包装类放在数组中存放,如果直接让 -128127之间的值自动装箱为Integer类型的对象,那么始终都会得到同一个对象,这是为了提升效率,因为小的数使用频率非常高,有些时候并不需要创建那么多对象,创建对象越多,内存也会消耗更多。

但是如果超出这个缓存范围的话,就会得到不同的对象了

判断值是否相等

equals()

Integer a = 128,b = 128;
sout(a.equals(b));

字符串转数字(除了Character外都有)

  • static int parseInt(String s) 将字符串类型的整数转成int类型的整数
Integer i = new Integer("666");
Integer i = new Integer.valueOf("666");
Integer i = new Integer.parseInt("666");

转进制(静态方法)

十六进制或八进制解码,转十进制

Integer i = Integer.decode("0xA6");

十进制转其他进制

  • static String toBinaryString(int i ) 转二进制
  • static String toOctalString(int i) 转八进制
  • static String toHexString(int i) 转十六进制

其他方法

  • Integer.sum(a,b)
  • Integer.max(a,b)
  • Integer.compare(a,b)

等等……

特别的

void也有包装类,其名为Void

并且,Void类无法new,只能令其为null:

Void v = null;
```  

## 特殊包装类

特殊包装类**没有自动装箱拆箱**,因此运算时要通过 `引用.方法名()` 进行运算

### BigInteger

可以用于**非常大整数**的运算

#### 导入

```java
import java.math.BigInteger;

创建

  1. 非静态
  • (int num,Random rnd) 获取随机大整数,[ 0,2的num次方-1]
  • (String val) 获取指定的大整数
  • (String val,int radix) 获取指定进制的大整数
  1. 静态
  • valueOf(long val) 获取BigInteger的对象,内部有优化

注:

  1. 但是能表示范围比较小,只能在long的取值范围之内
  2. 在内部对常用的数字: -16 ~16 进行了优化
    提前把 -16 ~16 先创建好BigInteger的对象,如果多次获取不会重新创建新的对象
  3. 一旦创建,内部记录值不能改变
  4. 只要进行计算都会产生一个新的BigInteger对象

不能通过 new 创建了,而要通过 .valueOf(值) 创建:

BigInteger i = BigInteger.valueOf(10000);

运算

  • add,subtract,multiply,divide 加减乘除
  • (返回数组) divideAndRemainder 除法,获取商和余数(0为商,1为余数)
  • equals 比较是否相同
  • pow 次幂
  • max / min
  • intValue / longValue / doubleValue 转为int / long / double型(注意不能超出范围)
BigInteger i = BigInteger.valueOf(10000);
i = i.multiply(BigInteger.valueOf(1000));   //将i的值乘1000后存入i中,注意1000的类型也是BigInteger

存储

以4字节为一块,存入数组

BigDecimal

可以用于小数的精确计算

导入

import java.math.BigDecimal;

创建

  1. 非静态
  • (double val) 这种方式有可能是不精确的,不建议使用
  • (String val)
  1. 静态
  • valueOf(double val) 也有范围

注:

  1. 如果数字不大,没有超出double的取值范围,建议使用静态
  2. 如果超出String构造
  3. 如果传递的是 [0,10] 的整数,那么方法会返回已经创建好的对象,不会重新new

运算

  • add , subtract , multiply , divide 加减乘除
  • divide(BigDecimal val,精确位数,舍入模式)

除不尽的时候使用

注:舍入模式
RoundingMode 类中,需要通过其中不同的枚举指定
UP : 远离零方向
DOWN : 向零方向
CEILING : 向正无穷
FLOOR : 向负无穷
HALF_UP : 四舍五入
HALF_DOWN : 五舍六入

存储

每一个数字(包括小数点)遍历后存入字节数组中(存放ASCII码)

不可变类

不可变类是指一旦创建其对象后,其状态(属性值)就不能被改变的类。这意味着你不能对一个不可变对象的属性进行修改。常见的不可变类有 StringIntegerLocalDate 等

定义

不可变类是在定义类时就加上了final,因此无法被继承和更改

字符串内容的不可变性

在 Java 中,String 对象本身是不可变的。这意味着一旦创建了一个 String 对象,它的内容不能被改变。你可以对 String 进行操作(例如,连接、切割等),但产生的结果将是一个新的 String 对象,而不改变原有的 String


Author: havenochoice
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source havenochoice !
评论
  TOC