【笔记】柏码 JavaSE 学习笔记(上)
柏码JavaSE学习笔记
面向过程篇
Java程序基础
程序代码基本结构
注释
变量与常量
基本数据类型
计算机中的二进制表示
整数类形
浮点类型
字符类型
布尔类型
运算符
赋值运算符
算术运算符
括号运算符
自增自减运算符
位运算符
关系运算符
逻辑运算符
流程控制
代码块与作用域
选择结构
循环结构
面向对象基础篇
类与对象
类的定义与对象创建
对象的使用
方法创建与使用
方法进阶使用
构造方法
静态变量和静态方法
包和访问控制
包声明和导入
访问权限控制
private
- 私有,标记为私有的内容无法被除当前类以外的任何位置访问。什么都不写
- 默认,默认情况下,只能被类本身和同包中的其他类访问。protected
- 受保护,标记为受保护的内容可以能被类本身和同包中的其他类访问,也可以被子类访问(子类我们会在下一章介绍)public
- 公共,标记为公共的内容,允许在任何地方被访问。
封装、继承和多态
类的封装
-
instanceof关键字:if (study instance of Teacher) xxx
-
单例模式:
public class Test {
private static Test instance;
private Test(){}
public static Test getInstance() {
if(instance == null)
instance = new Test();
return instance;
}
}
类的继承
-
final关键字:禁止继承父类 or 禁止重写方法
-
super关键字
(1) 访问父类的属性(当子类属性与父类属性同名,访问子类属性:name,访问父类属性:super.name)
(2) 调用父类的方法(当子类重写了父类的方法(@override),可以在重写的方法中super.eat(); 调用父类的eat(); 方法
(3) 显式调用带参构造(子类无参构造和有参构造都会自动调用父类的无参构造。如果父类没有无参构造方法,只有带参构造方法,则子类带参构造必须显式调用父类的带参构造方法:super(age)。在 Java 中,子类的构造方法必须直接或间接地调用父类的构造方法。这是因为子类需要确保父类的状态(属性初始化等)在自身初始化之前完成。)
顶层Object类
- p1.euals(p2)
方法的重写
抽象类
接口
- 实现接口更像是一个类的功能列表,作为附加功能存在,一个类可以附加很多个功能,接口的使用和继承的概念有一定的出入,顶多说是多继承的一种替代方案。
public class Student extends Person implements Study, A, B, C {
//多个接口的实现使用逗号隔开
}
- 并且接口没有继承数量限制,接口支持多继承:
public interface A exetnds B, C, D {
}
枚举类
// 枚举示例
public enum Status {
RUNNING("跑步"), STUDY("学习"), SLEEP("睡觉"); //无参构造方法被覆盖,创建枚举需要添加参数(本质就是调用的构造方法)
private final String name; //枚举的成员变量
Status(String name){
this.name = name;
}
public String getName() { //获取封装的成员变量
return name;
}
}
// 错误示例:
public enum Status {
RUNNING, STUDY, SLEEP; // 错误!缺少必要参数,因为默认构造函数已不存在
private final String name;
Status(String name) {
this.name = name;
}
}
// 以上正确枚举示例等价于:
public class Status {
public static final Status RUNNING = new Status("睡觉");
public static final Status STUDY = new Status("学习");
public static final Status SLEEP = new Status("睡觉");
private final String name;
private Status(String name) { // 私有构造函数
this.name = name;
}
public String getName() {
return name;
}
}
// 使用示例,以及参数的作用
public enum FileType {
IMAGE("图片", new String[]{"jpg", "png", "gif"}),
DOCUMENT("文档", new String[]{"pdf", "docx", "xlsx"}),
VIDEO("视频", new String[]{"mp4", "avi", "mov"});
private final String description;
private final String[] extensions;
FileType(String description, String[] extensions) {
this.description = description;
this.extensions = extensions;
}
public boolean isValidExtension(String ext) {
for (String validExt : extensions) {
if (validExt.equalsIgnoreCase(ext)) {
return true;
}
}
return false;
}
}
面向对象高级篇
基本类型包装类
包装类介绍
- Integer i = new Integer(10);
- Integer i = 10; 自动装箱
- int a = i.intValue();
- int a = i; 自动拆箱
- 通过自动装箱转换的Integer对象,如果值相同,得到的会是同一个对象,IntegerCache会默认缓存-128~127之间的所有值,将这些值提前做成包装类放在数组中存放
- 字符串转int:Integer i = new Integer("666");
- 字符串转int:Integer i = Integer.valueOf("5555");
- 对十六进制/八进制的字符串解码为int:Integer i = Integer.decode("0xA6");
- 十进制的整数转换其他进制的字符串:Integer.toHexString(166);
- Integer中提供的方法还有很多,这里就不一一列出了。
特殊包装类
- BigInteger表示超大的数
- BigInteger i = Biginteger.valueOf(Long.MAX_VALUE);
- i = i.multiply(Biginteger.valueOf(Long.MAX_VALUE));
- i = i.pow(100);
- BigDecimal表示需要精确计算的数
- BigDecimal i = BigDecimal.valueOf(10);
- i = i.divide(BigDecimal.valueOf(3),100,RoundingMode.CEILING);(计算10/3,精确到小数点后100位,最后一位向上取整)
- 无限循环的小数需要限制长度,防止异常。
数组
一维数组
-
数组也是对象,不是基本数据类型,所以创建时需要new
-
int[] array = new int[10];
-
int[] array = new int[]{1,2,3,4,5};
-
默认值是null(引用类型),0/false(基本数据类型)
-
长度属性:array.length 注意不是函数
-
不支持自动装箱和拆箱基本类型。
public static void main(String[] args) { int[] arr = new int[10]; Integer[] test = arr; } // 该写法不被允许
-
引用类型可以自由向上转型和向下转型
public static void main(String[] args) { String[] arr = new String[10]; Object[] array = arr; }
public static void main(String[] args) { Object[] arr = new Object[10]; String[] array = (String[]) arr; }
多维数组
-
int [][] array = new int[][]{{2,8,4,1},{9,2,0,3}}
-
即数组类型的数组
public static void main(String[] args) { int[][] array = new int[2][10]; int[][] array2 = { {1,2}, {3,4}, {5,6} } System.println(array2[2][1]); }
-
可变长参数
-
// 可变长参数 public void printArgs(String... args) { for (String arg : args) { System.out.println(arg); } } // 等价于数组参数 public void printArgs(String[] args) { for (String arg : args) { System.out.println(arg); } }
-
// 调用时可直接传入数组 String[] arr = {"a", "b", "c"}; printArgs(arr); // 合法,数组可直接传递给可变长参数
-
使用场景举例
// slf4j/log4j日志 // 传统方式(需拼接字符串) log.info("用户ID:" + userId + ",操作:" + operation); // 可变长参数方式 log.info("用户ID:{},操作:{}", userId, operation);
// 数据库操作 // 简化SQL参数设置 void executeQuery(String sql, Object... params) { // 内部将params作为数组处理 } executeQuery("SELECT * FROM users WHERE id IN (?)", 1, 2, 3); // 此外还有@RequestMapping等,不再一一列举
-
如果同时存在其他参数,那么可变长参数只能放在最后
字符串
String类
-
C/C++中的char占1个字节,Java中的char占用16位(即2字节),且无符号,取值范围是\u0000(0)到\uffff(65535),可存储Unicode字符。
-
Java的String内部基于char[]实现(Java9+改用byte[]优化)
-
每一个用双括号引起来的字符串,都是String示例对象。
-
String str = "Hello, world!";
-
String str = new String("Hello, world!");
-
如果是直接使用双引号创建的字符串,如果内容相同,为了优化效率,始终都是同一个对象。
public static void main(String[] args) { String str1 = "Hello World"; String str2 = "Hello World"; System.out.println(str1 == str2); } // true
-
如果是使用构造方法主动创建两个字符串对象,尽管内容相同,仍然是不同的对象。
public static void main(String[] args) { String str1 = new String("Hello World"); String str2 = new String("Hello World"); System.out.println(str1 == str2); } // false
-
判断两个字符串对象的内容是否相同:str1.equals(str2);
-
str.length() 方法获取字符串长度,注意与数组区分开,数组获取长度是读取length属性
-
str.substring(0,3)字符串的裁剪
-
str.split(" ")使用split方法进行字符串的分割,返回一个数组
public static void main(String args) { String str = "Hello, world!"; String[] strings = str.split(" "); for (String string : strings) { System.out.println(string); } }
-
字符数组和字符串之间可以快速进行相互转换
// string的toCharArray()方法,把字符串转换成字符数组 public static void main(String[] args) { String str = "Hello, world!"; char[] chars = str.toCharArray(); System.out.println(chars); } // String构造函数接受字符数组作为参数 public static void main(String[] args) { char[] chars = new char[]{'A','B','C'}; String str = new String(chars); System.out.println(str); }
StringBuilder类
- StringBuilder builder = new StringBuilder();
- builder.append("AAABBB");
- builder.delete(2,4);
- String str = builder.toString();
正则表达式
https://www.runoob.com/regexp/regexp-syntax.html
内部类
成员内部类
-
public class Test { public class Inner { public void test() { System.out.println("内部类TEST"); } } } public static void main(String[] args) { Test test = new Test(); Test.Inner inner = test.new Inner(); // 这里注意new前面加上test.,注意不是Test. }
-
成员内部类可以访问到外部的成员变量
-
外部类、内部类、参数的同名变量,同名方法
public class Test { private final String name; public Test(String name) { this.name = name; } public class Inner { String name; public void test(String name) { // ===同名变量=== System.out.println("方法参数的name = " + name); // 就近原则 name System.out.println("成员内部类的name = " + this.name); // this.name System.out.println("成员外部类的name = " + Test.this.name); // Test.this.name // ===同名方法=== this.toString(); // inner的toString()方法[1] super.toString(); // inner的父类Object的toString()方法[2] Test.this.toString(); // 外部类Test的toString()方法[3] Test.super.toString(); // 外部类父类Object的toString()方法[4] // 其中[1][2]内容相同,[3][4]内容相同。 } } }
静态内部类
-
public class Test { private final String name; public Test(String name) { this.name = name; } public class Inner1 { public void test() { System.out.println("普通内部类TEST"); } } public static class Inner { public void test() { System.out.println("静态内部类TEST"); } } public static void main(String[] args) { // 普通内部类创建 Test test = new Test(); Test.Inner1 inner1 = test.new Inner(); inner1.test(); // 静态内部类创建 Test.Inner inner = new Test.Inner(); inner.test(); } }
-
在静态内部类中,整个内部类处于静态上下文,因此无法访问到外部类的非静态内容
局部内部类
-
像局部变量一样,可以在方法中定义。
public class Test { private final String name; public Test(String name) { this.name = name; } public void hello() { class Inner { public void test(){ System.out.println("我是局部内部类"); } } Inner inner = new Inner(); inner.test(); } }
匿名内部类
-
对于抽象类和接口,不能new创建实例,但是可以使用匿名内部类。
-
正常情况下,要创建一个抽象类的实例对象,只能对其进行继承,先实现未实现的方法,然后创建子类对象。
而我们可以在方法中使用匿名内部类,将其中的抽象方法实现,并直接创建实例对象:
public abstract class Student { public abstract void test(); } public static void main(String[] args) { Student student = new Student() { @Override public void test() { System.out.println("匿名内部类的实现"): } } student.test(); }
Lambda表达式
-
如果一个接口中有且只有一个待实现的抽象方法,那么我们可以将匿名内部类简写为Lambda表达式
public static void main(String[] args) { Study study1 = new Study() { @Override public void study() { System.out.println("学习"); } } study1.study(); // 等价于 Study study = () -> System.out.println("学习"); study.study(); }
-
Lambda仅支持接口,不支持抽象类
-
常见几种情况
Study study = (a) -> { System.out.println("学习"); return "学"+a; }; // 注意有分号; System.out.println(study.study(10)); // 一个参数,有返回值
Study study = (a) -> { return "学"+a; }; // 方法体只有一个return语句,可简化为 Study study = (a) -> "学"+a; // 参数只有1个,可以省去小括号,简化为 Study study = a -> "学"+a;
// 如果一个方法的参数需要的是一个接口的实现,参数也可写成Lambda表达式 public static void main(String[] args) { test(a -> "学"+a); } private static void test(Study study) { study.study(10); }
方法引用
-
方法引用就是将一个已实现的方法,直接作为接口中抽象方法的实现
-
直接将已有方法的实现作为接口的实现:
public static void main(String[] args) {
Study study = (a, b) -> Integer.sum(a, b);
System.out.println(study.sum(10, 20));
}
- 改成使用方法引用:
public static void main(String[] args) {
Study study = Integer::sum;
// 使用双冒号来进行方法引用,静态方法使用 类名::方法名 的形式
System.out.println(study.sum(10, 20));
}
异常机制
异常的类型
自定义异常
抛出异常
异常的处理
断言表达式
常用工具类介绍
数学工具类
-
java.lang包下的类,默认可以直接使用
-
Math.pow(5,3);
-
Math.sin(Math.PI / 2);
-
Math.log(Math.E);
-
Math.log10(100);
-
Math.ceil(4.5); 向上取整
-
Math.floor(5.6); 向下取整
-
Random 随机数生成(java.util包下的类,需要导入使用)
public static void main(String[] args) { Random random = new Random(); //创建Random对象 for (int i = 0; i < 30; i++) { System.out.print(random.nextInt(100)+" "); //nextInt方法可以指定创建0 - x之内的随机数 } }
数组工具类
-
String str = Arrays.toString(arr); 数组转字符串:[1,4,5,8]
-
Arrays.sort(arr); 数组升序排序
-
Arrays.fill(arr,66); 数组快速填充
-
Arrays.copyOf(arr,10); 数组拷贝
public static void main(String[] args) { int[] arr = new int[]{1,2,3,4,5}; int[] target = Arrays.copyOf(arr,5);//如果是第二个参数是新数组的大小,如果是10,多余的部分用0填充 System.out.println(Arrays.toString(target)); System.out.println(arr == target);//false,比较的是内存地址而不是内容,内容比较应该用工具类 }
public static void main(String[] args) { int[] arr = new int[]{1, 2, 3, 4, 5}; int[] target = Arrays.copyOfRange(arr, 3, 5); //也可以只拷贝某个范围内的内容 System.out.println(Arrays.toString(target)); System.out.println(arr == target); }
public static void main(String[] args) { int[] arr = new int[]{1, 2, 3, 4, 5}; int[] target = new int[10]; System.arraycopy(arr, 0, target, 0, 5); //使用System.arraycopy进行搬运 System.out.println(Arrays.toString(target)); }
-
Arrays.binarySearch(arr, 5);
public static void main(String[] args) { int[] arr = new int[]{1, 2, 3, 4, 5}; System.out.println(Arrays.binarySearch(arr, 5)); //二分搜索仅适用于有序数组 }
-
Arrays.equals(arr1,arr2);
public static void main(String[] args) { int[][] array = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}}; System.out.println(Arrays.deepToString(array)); //deepToString方法可以对多维数组进行打印 }
public static void main(String[] args) { int[][] a = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}}; int[][] b = new int[][]{{2, 8, 4, 1}, {9, 2, 0, 3}}; System.out.println(Arrays.equals(a, b)); //equals仅适用于一维数组 System.out.println(Arrays.deepEquals(a, b)); //对于多维数组,需要使用deepEquals来进行深层次判断 }
泛型程序设计
泛型
泛型类
-
public class Score<T> { String name; String id; T value; public Score(String name, String id, T value) { this.name = name; this.id = id; this.value = value; } } public static void main(String[] args) { Score<String> score = new Score<String>("计网","1","优"); // 在使用时,需要跟上<>并在其中填上具体类型。 String value = score.value; // 一旦类型明确,那么泛型就变成对应的类型了。 System.out.println(value); }
-
如果要让某个变量支持引用确定了任意类型的泛型,那么可以使用
?
通配符public static void main(String[] args) { Test<?> test = new Test<Integer>(); test = new Test<String>(); Object o = test.value; //但是注意,如果使用通配符,那么由于类型不确定,所以说具体类型同样会变成Object }
-
泛型变量可以是多个
public class Test<A,B,C> { public A a; public B b; public C c; } public static void main(String[] args) { Test<String, Integer, Character> test = new Test<>(); // 使用钻石运算符可以省略其中的类型。 test.a = "hello"; test.b = 123; test.c = "好"; }
-
泛型只能确定为一个引用类型,而不支持基本数据类型
public static void main(String[] args) { Test<int> test = new Test<int>(); } // 以上写法是错误的!
public static void main(String[] args) { Test<int[]> test = new Test<>(); } // 注意,因为数组本身是引用类型,所以该写法正确
泛型与多态
-
不只是类,接口与抽象类都支持泛型。
public interface Study<T> { T test(); } public class Main { static class A impements Study<Integer> { // 在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确引用类型 @Override public Integer test() { return Integer.valueOf(100); } } static class B implements Study<T> { // 也可以让子类继续是一个泛型类,不明确引用类型 @Override public T test() { return null; } } public static void main(String[] args) { A a = new A(); Integer i = a.test(); } }
// 类继承同上 static class A<T> { } static class B extends A<String> { }
泛型方法
-
在函数返回值前面加
<T>
表示这是一个泛型方法public class Main { private static <T> T test(T t) { return t; } public static void main(String[] args) { String str = test("Hello,world!"); } } // 泛型方法会在使用的时候自动确定类型,上面的test("Hello,world!")传入字符串类型,那么T自动变成String类型
-
使用场景示例:Arrays的排序方法
Integer[] arr = {1,4,5,2,6,3,0,7,9,8}; Arrays.sort(arr,new Comparator<Integer>(){ // 创建泛型接口Comparator的匿名内部类 @Override public int compare(Integer o1, Integer o2) { return o2 - o1; // 降序排列 } });
-
利用Lambda表达式
public static void main(String[] args) { Integer[] arr = {1,4,5,2,6,3,0,7,9,8}; Arrays.sort(arr, (o1,o2) -> o2 - o1); System.out.println(Arrays.toString(arr)); }
-
使用场景示例:Arrays数组复制方法
public static void main(String[] args) { String[] arr = {"AAA","BBB","CCC"}; String[] newArr = Arrays.copyOf(arr,3); // 这里传入的是什么类型,返回的就是什么类型,就是用到了泛型 System.out.println(Arrays.toString(newArr)); }
泛型的界限
-
public class Score<T extends Number> { // 设定类型参数上届,必须是Number或者Number的子类 private final String name; private final String id; private final T value; public Score(String name, String id, T value) { this.name = name; this.id = id; this.value = value; } public T getValue() { return value; } }
-
public static void main(String[] args) { Score<? extends Integer> score = new Score<>("数据结构与算法", "EP074512", 60); // 泛型通配符也支持泛型的界限 }
-
// 设定类型参数下限:Score<? super Number> public static void main(String[] args) { Score<? super Number> score = new Score<>("数据结构与算法基础", "EP074512", 10); Object o = score.getValue(); }
类型擦除
-
public static void main(String[] args) { Test test = new Test(); //对于泛型类Test,不指定具体类型也是可以的,默认就是原始类型 }
-
原理详见文档
函数式接口[TODO]
-
函数式接口就是JDK1.8专门为我们提供好的用于Lambda表达式的接口
-
Cosumer<T>
:接收一个参数,不返回结果,用于消费数据。Consumer<String> printer = s -> System.out.println(s); printer.accept("Hello");
-
Function<T,R>
:接收一个参数,返回一个结果。Function<Integer, String> converter = num -> "结果:" + num; System.out.println(converter.apply(100));
-
Predicate<T>
:接收一个参数,返回布尔值,用于判断。Predicate<Integer> isEven = num -> num%2==0; System.out.println(isEven.test(4));
-
Supplier<T>
:不接收参数,返回一个结果。Supplier<Double> randomSupplier = () -> Math.random(); System.out.println(randomSupplier.get());
-
函数式接口是 lambda 表达式的基础。当一个接口被标注为
@FunctionalInterface
后,就可以用 lambda 表达式来简洁地实现它。@FunctionalInterface interface Calculator { int calculate(int a, int b); } public class Main { public static void main(String[] args) { // 使用 lambda 表达式实现 Calculator 接口 Calculator add = (a, b) -> a + b; Calculator subtract = (a, b) -> a - b; System.out.println(add.calculate(5, 3)); // 输出 8 System.out.println(subtract.calculate(5, 3)); // 输出 2 } }
判空包装
-
public static void main(String[] args) { test(null); } private static void test(String str) { if(str == null) return; //防止null导致的异常 if(!str.isEmpty()) { System.out.println("字符串长度为:"+str.length()); } } // 利用Optional优雅地处理空指针: private static void test(String str) { Optional.ofNullable(str) .ifPresent(s -> System.out.println("字符串长度为:+s.length()")) // 如果str不为空,执行这里的Consumer函数式接口实现 }
-
private static void test(String str){ String s = Optional.ofNullable(str).orElse("我是为null的情况备选方案"); System.out.println(s); }
泛型通配符
-
生活类比:收快递 vs 拆快递
“关心具体类型” 的场景(类似用T)
你开了一家水果店,要求快递员只送 “苹果箱” 或 “香蕉箱”,并且需要知道箱子里具体是哪种水果(比如要给苹果贴红色标签,给香蕉贴黄色标签)。
关键点:必须明确知道具体类型(苹果 / 香蕉),才能进行针对性操作。
“不关心具体类型” 的场景(类似用?)
你是仓库管理员,只负责 “称重所有箱子” 或 “把箱子堆到货架上”,不管箱子里装的是苹果、香蕉还是其他水果。
关键点:只要箱子是 “装东西的容器”,就可以统一处理,不需要知道里面具体是什么。 -
// 定义泛型类,使用T作为类型参数 public class GenericList<T> { private List<T> list = new ArrayList<>(); public void add(T element) { list.add(element); } public T get(int index) { return list.get(index); } // 方法参数使用通配符?,表示不关心具体类型,仅读取元素 public static void printAll(GenericList<?> list) { for (Object element : list.list) { System.out.println(element); } } // 方法参数使用T,保持类型一致性(添加元素时需要明确类型) public static <T> void copy(GenericList<T> source, GenericList<T> dest) { for (T element : source.list) { dest.add(element); } } }
数据结构基础
线性表:顺序表
线性表:链表
线性表:栈
线性表:队列
树:二叉树
树:二叉查找树和平衡二叉树
树:红黑树
哈希表
实战练习
反转链表
括号匹配问题
实现计算器