Java 基础

83 阅读13分钟

构造方法

构造方法又叫构造器,是类的一种特殊方法,它的主要作用是完成对新对象的初始化。它有几个特点:

  • 构造器的修饰符可以是public、缺省、protected、private

  • 一个类可以定义多个不同的构造器,即构造器的重载

  • 未手动定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器)

    可以使用 javap指令,反编译查看

    // 默认构造器
    class Dog{
        Dog(){
        
        }
    }
    
  • 一旦定义手动定义构造器,默认构造器就被覆盖,不能在使用默认的无参构造器,除非显示的定义无参构造器

    class Dog{
        // 手动定义无参构造器
        Dog(){
        
        }
        public Dog(String dName){
            // ...
        }
    }
    
  • 构造器没有返回值,也不能写 void

  • 方法名必须和类名一样

  • 参数列表和成员方法的规则一样

  • 构造器的调用由系统完成

作用域

全局变量

也就是类的成员属性,作用域为整个类体,可以不赋值直接使用,因为有默认值。

局部变量

除了成员属性之外的其他变量(代码块、方法中),作用域为定义它的代码块中,必须赋值后才能使用,因为没有默认值。

使用细节

  • 成员变量和局部变量可以重名,访问时遵循就近原则
  • 在同一个作用域中,比如在同一个成员方法中,两个局部变量不能重名
  • 成员变量声明周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变量,声明周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡。
  • 全局变量可以加修饰符,局部变量不可以加修饰符(Modifier 'xxxx' not allowed here)。

包的使用细节

访问修饰符

各类修饰符总结

修饰符权限

NO范围privatedefaultprotectedpublic
1同一包中的同一类可以访问可以访问可以访问可以访问
2同一包中不同类不可以访问可以访问可以访问可以访问
3不同包中的子类不可以访问不可以访问可以访问可以访问
4不同包中的非子类不可以访问不可以访问不可以访问可以访问

注意事项

  • 修饰符可以用来修饰类中的属性、方法以及类
  • 只有默认的和public才能修饰类,并且遵循以上访问权限特点
  • 成员方法的访问规则和属性完全一样

多态

方法的多态

  • 重写方法

    不同子类调用重写父类的方法,体现多态

  • 重载方法

    方法名相同,参数列表和返回值不同,体现出来多态

对象的多态

对象多态的前提是两个类存在继承关系。

向上转型

本质:父类的引用指向子类的对象

语法:父类类型 引用名 = new 子类类型();

特点:编译类型看左边,运行类型看右边,可以调用父类中所有成员(需要遵循访问权限规则),不能调用子类中特有成员,最终运行效果看子类具体实现。

// Animal.java
public class Animal{
    private String name;
    
    public void cry(){
        sout("动物叫。。。");
    }
    
    public void info(){
        sout("我叫" + this.name);
    }
    
    private void sleep(){
        sout(this.name + "睡觉");
    }
    
    public Animal(String name){
        this.name = name;
    }
    
    public String getName(){
        return this.name;
    }
    
    public void setName(String name){
        this.name = name;
    }
}
// Cat.java
public class Cat extends Animal{
    @Override
    public void cry(){
        sout("小猫喵喵叫。。。");
    }
    
    public void run(){
        System.out.println(this.getName() + "跑步。");
    }
    
    public Cat(String name){
        super(name);
    }
}
// Dog.java
public class Dog extends Animal{
    @Override
    public void cry(){
        sout("小狗汪汪叫。。。");
    }
    
    public Dog(String name){
        super(name);
    }
}
// Poly01.java
public class Poly01{
    public static void main(String[] args){
        // animal 的编译类型为Animal,运行类型为 Cat
        Animal animal = new Dog("大黄狗");
​
        // animal 的编译类型为Animal,运行类型为 Cat
        animal = new Cat("大橘猫");
        
        // 不能调用子类特有的方法
        // 因为在编译阶段 animal 的类型为 Animal,而Animal 类型不存在 run()方法,报错
        // animal.run(); 
        
        // 编译时,animal 为 Animal 类型,Animal 类存在 info 方法,
        // 运行时,animal 为 Cat 类型,Cat 类不存在 info 方法,调用父类的 info 方法
        animal.info();
        
        // 遵循修饰符规则,不能使用父类 private 成员
        // animal.sleep(); 报错
    }
}

向下转型

语法:子类类型 引用名 = (子类类型) 父类引用;

注意:父类引用必须指向当前类型的实例

// 向下转型
// cat 的编译类型和运行类型都是 Cat
// 父类的引用必须指向的是当前目标类型的对象,即 animal 指向的是 Cat 类的实例(见Poly02.java)
Cat cat = (Cat) animal;
// Dog dog = (Dog) animal; // 报错,因为此时 animal 引用的是 Cat 类型
// 可以调用子类特有的方法
cat.run();

JAVA 动态绑定机制

java 继承之上——动绑机制详解

在java 面向对象三大特性——继承篇中,我们说过java 中查找方法的顺序为 : 本类方法—>父类方法—>更高一级的父类—>......Object(顶层父类) 。然而,在某些情况下,这样的原则也会被凌驾。今天我们要说的java动态绑定机制,就是这样一个区别于继承机制的例外。

  • 当通过对象的形式调用方法时,该方法会和堆内存中真正的该对象——的内存地址绑定,且这种绑定关系会贯穿方法的执行全过程。这就是所谓的“动态绑定机制”。
  • 当通过对象的形式调用属性时,不存在动态绑定机制,符合继承机制——即java中查找变量的顺序 : 局部变量—>成员变量—>父类—>更高的父类—>......—>Object 。

  • 当 B 类的 sum 和 sum1 方法不被注释

    40

    30

  • 当 B 类的 sum 和 sum1 方法被注释

    30

    20

I/O流

文件

文件流

文件在程序中是以流的形式来操作的

流:数据在数据源(文件)和程序(内存)之间经历的路径

输出流:数据从程序(内存)到数据源(文件)之间的路径

输入流:数据从数据源(文件)之间到程序(内存)之间的路径

创建文件

案例:在E盘下,创建文件 news1.txt、news2.txt、news3.txt,用三种不同的方式创建。

File newFile = new File(String filePath);
File newFile2 = new File(String parentDir, String childPath);
file newFile3 = new File(File ParentFile, String childPath);

文件信息

  • getAbsolutePath(); 文件绝对路径
  • getName(); 文件名字
  • getParent(); 文件父级目录
  • length(); 文件大小(字节)
  • exists(); 文件是否存在
  • isFile(); 是不是一个文件
  • isDirectory(); 是不是一个目录

I/O流的分类

  • 按操作数据单位不同分

    1. 字节流
    2. 字符流

    字节和字符的区别?

  • 按数据流向不同

    1. 输入流
    2. 输出流
  • 按流的角色不同

    1. 节点流
    2. 处理流/包装流
抽象基类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

InputStream 字节输入流

InputStream 是抽象类 是所有类字节输入流的超类

InputStream 常用子类

FileInputStream (Java SE 11 & JDK 11 ) (runoob.com)

  • FileInputStream 文件输入流
  • ObjectInputStream 对象字节输入流
  • BufferedInputStream 缓冲字节输入流

OutputStream 字节输出流

OutputStream 是抽象类 是所有类字节输出流的超类

OutputStream 常用子类

FileOutputStream (Java SE 11 & JDK 11 ) (runoob.com)

泛型

泛型的理解和好处

不使用泛型

import java.util.ArrayList;
​
class Dog{
    private String name;
    private int age;
​
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
​
    public String getName() {
        return name;
    }
​
    public int getAge() {
        return age;
    }
}
​
​
public class Generic01 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
​
        // ArrrayList 会把添加进去的对象进行向上转型,即 Dog -> Object
        arrayList.add(new Dog("大黄",3));
        arrayList.add(new Dog("二黄",2));
        arrayList.add(new Dog("三黄",1));
​
        // arrayList.add(new Cat("大橘", 5)); 不小心添加一只猫
​
        for(Object o : arrayList){
            Dog dog = (Dog)o; // 向下转型
            System.out.println(dog.getName() + dog.getAge());;
        }
    }
}

不使用泛型的问题:

  1. 不能对加入到集合 ArrayList 中的数据类型进行约束(不安全)
  2. 遍历的时候,需要进行类型转换,如果集合中数据量较大,对效率有影响

使用泛型

public class Generic02 {
    public static void main(String[] args) {
        ArrayList<Dog> arrayList = new ArrayList<Dog>();
​
        arrayList.add(new Dog("大黄",3));
        arrayList.add(new Dog("二黄",2));
        arrayList.add(new Dog("三黄",1));
​
        // arrayList.add(new Cat("大橘", 5)); 此时泛型会编译报错
​
        for(Dog dog : arrayList){
            // Dog dog = (Dog)o; // 不需要向下类型转换
            System.out.println(dog.getName() + dog.getAge());
        }
    }
}

泛型的好处:

  1. 编译时检查添加元素的类型,提高了安全性
  2. 减少类型转换的次数,提高效率

泛型注意事项

  1. 类型参数只能是引用类型,不能是基本数据类型

    ArrayList<String> arrayList = new ArrayList<>();
    ArrayList<int> arrayList = new ArrayList<>(); // Type argument cannot be of primitive type
    
  2. 在给泛型指定具体类型后,可以传入该类型或其子类,本质上就是向上转型

    package com.hsy.generic;
    ​
    public class Generic03 {
        public static void main(String[] args) {
            Pig<A> pig = new Pig<>(new A());
            // 编译时和运行时类型都是 A
            pig.getClassName(); // class com.hsy.generic.A
            // 在给泛型指定具体类型后,可以传入该类型或其子类,本质上就是向上转型
            Pig<A> pig2 = new Pig<>(new B());
            // 编译时类型是 A,运行时类型是 B
            pig2.getClassName(); // class com.hsy.generic.B
        }
    }
    ​
    class A{}
    class B extends A{}
    class Pig<E>{
        E e;
    ​
        public Pig(E e) {
            this.e = e;
        }
    ​
        public void getClassName(){
            System.out.println(e.getClass());
        }
    }
    
  3. 泛型默认类型为 Object

    韩顺平Java_泛型使用细节2

    ArrayList arrayList = new ArrayList();
    // 等价于 ArrayList<Object> arrayList = new ArrayList<>();
    

泛型声明

泛型类

class ClassName<T, K, R, ...>{
    
}

注意事项:

  1. 普通成员可以使用泛型(属性、方法)

泛型接口

interface InterfaceName<T,K>{}

泛型实例化

面向对象

对象在内存中的存储

image-20240102213107952

说明

  1. 执行 Cat cat = new Cat(); 创建 cat 对象时,会在方法区中加载 Cat 类属性和方法信息,且只会加载一次
  2. 在堆中分配空间,进行默认初始化
  3. 把地址赋给p,p就指向堆中的对象
  4. 字符串存储在方法区的常量池中

枚举类

异常

异常体系图

image-20231217144942706

  • 异常分为两大类:运行时异常和编译时异常
  • 运行时异常编译器检查不出来。一般指编程时的逻辑错误面试程序员应该避免其抛出异常。java.lang.RuntimeException类及其子类都是运行时异常。对于运行时异常可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
  • 编译时异常,是编译器要求必须处理的异常。

常见运行时异常

  • NullPointerException 空指针异常

    // 当使用一个引用指向 null 的对象的成员时
    String name = null;
    sout(name.length());
    
  • ArithmeticException 数学运算异常

  • ArrayIndexOutOfBoundsException 数组下标越界异常

  • ClassCastException 类型转换异常

    class A {}
    class B extends A {}
    class C extends A {}
    public class ClassCastExceptionTest {
        public static void main(String[] args) {
            A a = new B();
            B b = (B)a;
            C c = (C)a; // ClassCastException 类型转换异常
        }
    }
    
  • NumberFormatException 数字格式不正确异常

    // 当把字符串转换为数值类型,但该字符串不能转换为适当格式时,抛出该异常
    String name = "衡盛永";
    int num = Integer.parseInt(name);
    

    编译异常

    image-20231217151802233

异常处理机制

try-catch-finally

image-20231217152430581

try {
    String name = null;
    System.out.println(name.length()); // 异常
    System.out.println("异常发生后try中的的代码不再被执行,直接进入到catch块");
} catch (Exception e) {
    System.out.println(e.getMessage());; // 输出异常信息
}
System.out.println("程序继续执行");

可以有多个catch语句捕获不同的异常,要求父类异常在后,子类异常在前,比如(Exception 在后,NullPointerException 在前),如果发生异常,只会匹配一个catch。

try {
    String name = "衡盛永";
    name = null;
    System.out.println(name.length()); // NullPointerException
​
    int num1 = 10;
    int num2 = 0;
    int res = num1 / num2;
}
catch(NullPointerException e){
    System.out.println(e.getMessage()); // 捕获并处理异常,后面的catch不再执行
}
catch(ArithmeticException e){
    System.out.println(e.getMessage());
}
catch (Exception e) {
    e.printStackTrace();
}

image-20231217160148558

try{
    // 可能会发生异常的代码
} finally {
    // 如果发生异常,执行完 finally 块的代码,程序直接直接退出
}
// 如果发生异常,后面的代码不再被执行

练习题

如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止。

throws

image-20231217153208321

注意

1. try-catch-finally 和 throws 方式二选一

2. 如果既没有显式的try-catch-finally也没有 throws,那么默认是使用throws的,这就是当没有对异常进行处理时JVM最终会输出异常信息并退出程序的原因。

数据库

分组统计

测试表

CREATE TABLE dept( -- 部门表
    deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, -- 部门编号
    dname VARCHAR(20) NOT NULL DEFAULT "", -- 部门名称
    loc VARCHAR(13) NOT NULL DEFAULT "" -- 部门地址
);
INSERT INTO dept VALUES(10, 'ACCOUNTING', 'NEW YORK'), (20, 'RESEARCH', 'DALLAS'), (30, 'SALES', 'CHICAGO'), (40, 'OPERATIONS', 'BOSTON');

反射

反射快速入门

image-20240123230902051

package com.hsy.reflect;
​
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Properties;
​
@SuppressWarnings("all")
public class Reflect01 {
    public static void main(String[] args) throws Exception {
        // 根据 re.properties 配置文件,创建 Cat 对象并调用 hi 方法
        // 反射
        // 使用 Properties 类,可以读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\re.properties"));
        String classfullPath =  properties.get("classfullPath").toString();
        String methodName =  properties.get("method").toString();
        System.out.println(classfullPath instanceof String);
        System.out.println(methodName instanceof String);
        // 反射机制
        // 加载类,返回 Class 类型的对象 cls
        Class cls = Class.forName(classfullPath);
        // 通过 cls 得到加载的类 com.hsy.reflect.Cat 类的实例
        Object o = cls.newInstance();
        System.out.println(o.getClass()); // 获取对象的运行时类型 Cat
        // 通过 cls 得到加载类 com.hsy.reflect.Cat 的成员方法 methodName'hi' 对象
        Method method1 = cls.getMethod(methodName);
        // 通过成员方法对象实现方法的调用
        method1.invoke(o); // 传统方法 对象.方法名()  反射机制 方法对象.invoke(对象);
    }
}
​
class Cat{
    private String name = "招财猫";
​
    public void hi(){
        System.out.println("hi" + this.name);
    }
}

反射原理

  1. 反射机制允许程序在执行期间借助于Reflection API 取得任何类的内部信息(比如成员变量、构造器、成员方法等等),并能操作对象属性及方法。反射在设计模式及框架底层都会用到。
  2. 加载完类后,在堆中就生成了一个Class类型的对象(一个类只有一个Class对象,即一个类只能被加载一次),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为反射

image-20240123231651627

数据库

创建数据库

数据库函数

分组函数

统计函数

字符串函数

数学函数

日期函数

-- 在MYSQL中,日期类型可以直接比较,需要注意格式:'YYYY-MM-DD'
-- 查询1992.1.1后入职的员工
SELECT * FROM emp
    WHERE hiredate > '1992-01-01';

加密函数

流程控制函数

IF(expre1, expre2, expre3) -- 如果expre1为TRUE,则返回expre2,否则返回expre3
-- 如果comm是NULL,则显示0.0,判断是否为NULL,要使用 IS NULL,判断不为 NULL 使用 IS NOT NULL
SELECT IF(comm IS NULL, 0.0, comm) FROM function_if_employee;
IFNULL(expre1, expre2) -- 如果expre1不为NULL,则返回expre1,否则返回expre2
SELECT IFNULL(comm, 0.0) FROM function_if_employee;
-- 如果job是 CLERK,则显示 职员,如果是 SALEMAN,则显示 销售人员,如果是 MANAGER,则显示 经理,其他正常显示
SELECT ename,
    (SELECT CASE
        WHEN job = 'CLERK' THEN '职员'
        WHEN job = 'SALEMEN' THEN '销售人员'
        WHEN job = 'MANGER' THEN '经理'
        ELSE job END) AS job, job
            FROM function_if_employee;

多表查询