Java2023
注意事项
代码规范!
使用GBK是因为老师要授课,要在DOS系统中显示中文,所以用GBK。一般编码都是用UTF-8
DOS系统
…\ 是从当前目录返回到上一级目录
cd /D c: 中,/D 可切换根目录。
JRE JVM JDK 的关系
JDK = JRE + Java开发工具
JRE = JVM + 核心类库
环境变量path配置及其作用
- 环境变量的作用是为了在dos的任意目录可以使用java和javac的命令
- 先配置JAVA_HOME = 指向jdk安装的主目录
- 编辑path环境变量,增加%JAVA_HOME%\bin+++
Java中判断字符串相等,不能用==,假设String a = “12454”;
判断a是否和字符串12454相等的方法:
a.euqals("12454")
使用Java从键盘输入数据
import java.util.Scanner;
public class Input {
public static void main(String[] args){
/*
* 1. Scanner类表示简单文本扫描器,在java.util包
* 2. 导入Scanner类所在的包
* 3. 创建Scanner对象,new一个,
*/
Scanner scanner = new Scanner(System.in);
String name = scanner.next(); //接收用户的string
int age = scanner.nextInt(); //接受用户的年龄 int
double salary = scanner.nextDouble(); //接受用户的薪水 salary
System.out.println("name="+ name +" age=" + age + " salary=" + salary);
scanner.close();
}
}
使用Acwing编写java
import java.util.Scanner;
public class Main{
public static void main(String[] args){
int a,b;
Scanner scanner = new Scanner(System.in);
a = scanner.nextInt();
b = scanner.nextInt();
System.out.println(a+b);
}
}
使用java随机数
import java.util.Random;
public class RANDOM{
public static void main(String[] args){
int[] a = new int[10];
Random random = new Random();
for(int i=0;i<a.length;i++)
a[i] = random.nextInt(100);
for(int i=0;i<a.length;i++)
System.out.print(a[i] + " ");
}
}
使用java实现冒泡排序
import java.util.Random;
public class BubbleSort {
public static void main(String[] args){
int[] a = new int[10];
Random random = new Random();
for(int i=0;i<a.length;i++)
a[i] = random.nextInt(100);
for(int i=0;i<a.length;i++)
System.out.print(a[i] + " ");
int n = a.length;
for(int i=1;i<n;i++)
for(int j=0;j<n-i;j++)
if(a[j] > a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
System.out.println();
for(int i=0;i<a.length;i++)
System.out.print(a[i] + " ");
}
}
- 可变参数的使用,
int... nums
表示接受任意多个int。可以理解为nums是个数组 - 可变参数可以和其余形参放在一起,但可变参数在最后
正则表达式
原理
目标:匹配所有四个数字
- \d表示任何一个数字
- 创建模式对象[即正则表达式对象]
- 创建匹配器
//说明:创建匹配器matcher,按照正则表达式的规则pattern 去匹配content字符串 - 开始匹配
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ReGTheory {
public static void main(String[] args){
String content = "Java 的历史要追溯到 1991 年,由 Sun公1998司带领的开2456发小组,"+
"想设计一种小型的计算机语言,主要用于有线电视转换盒这类的消费设备。"+
"由于这类设备的处理能力和内存都非常有限,所以语言就必须设计的非常小且能够生成紧凑的代码。"+
"另外,由于不同的厂商会选择不同的 CPU, 因此很重要的一点就是这种语言不应该与任何特定的体系结构绑定。这个项目被命名为 “Green”。1996 年"+
", Sun 发布了第一个 Java 1.0 版本。 ";
//1.生成正则表达式
String regStr = "(\\d\\d)(\\d\\d)";
//2.创建模式对象[即正则表达式对象]
Pattern pattern = Pattern.compile(regStr);
//3.创建匹配器
//说明:创建匹配器matcher,按照正则表达式pattern的规则去匹配content字符串
Matcher matcher = pattern.matcher(content);
//4.开始匹配
while(matcher.find()){
System.out.println("找到"+matcher.group(0));
}
}
}
matcher.find()完成的任务
- 根据指定的规则,定位满足规则的子字符串
- 找到后,将该子字符串的开始的索引记录到matcher对象的属性int[] groups中.
以1991为例,此时groups[0] = 13,把该子字符串的结束的索引+1的值记录到groups[1]=17 - 同时记录oldLast的值为子字符串的结束的索引值+1,即4.即下次执行find时,从4开始匹配。
matcher.group(0)分析
源码:
public String group(int group) {
if (first < 0)
throw new IllegalStateException("No match found");
if (group < 0 || group > groupCount())
throw new IndexOutOfBoundsException("No group " + group);
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
return null;
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
}
根据groups[0]=13 和groups[1]= 17的记录的位置,从content开始截取子字符串(从groups[0]~groups[1])返回。
如果再次使用find方法,仍然按照上面分析来执行。后面再用时,groups[0]和groups[1]的值就变成下一个匹配的子字符串的位置。
分组为什么是group(0),有什么含义?
groups[0] groups[1]分别表示整个匹配到的子字符串的起止位置。
groups[2]和groups[3]分别表示第一个分组的起止位置,第1组即group(1)
groups[4]和groups[5]分别表示第二个分组的起止位置,第2组即group(2)
groups[6]和groups[7]分别表示第三个分组的起止位置,第3组即group(3)
所以上面为什么是get-Sequence(groups[group * 2], groups[group * 2 + 1])呢
整个是0*2=0,0*2+1=1,所以groups[0] groups[1]表示整个的子字符串
第一组group(1):1*2=2,1*2+1=3
第二组group(2): 2*2=4, 2*2+1=5
第三组group(3):3*2=6,3*2+1=7.
小结:
如果正则表达式有() 即分组:
取出匹配的字符串规则如下:
group(0)表示匹配到的整个子字符串
group(1)表示匹配到的子字符串的第1组字符串
group(2)表示匹配到的子字符串的第2组字符串
group(3)表示匹配到的子字符串的第3组字符串
下标注意不能越界。分组数不能越界。
正则转义符中,Java中两个反斜杠才代表一个反斜杠。\( 才能匹配到(。
(?i)abc 表示不区分大小写abc。
在编译正则表达式时,直接就选择忽略大小写:
Pattern pattern = Pattern.compile(regStr,Pattern.CASE_INSENSITIVE);
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.find()){
System.out.println("匹配成功");
}
else System.out.println("匹配失败");
//验证url
String content = "https://www.bilibili.com/video/BV1Eq4y1E79W/?p=17&spm_id_from=pageDriver&vd_source=4283306641c7c0c69e8d362d4f1b4e97";
String regStr = "^((http|https)://)([\\w-]+\\.)+[\\w-]+(\\/[\\w-?=&#/%.]*)?$";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
if(matcher.find()){
System.out.println("匹配成功");
}
else System.out.println("匹配失败");
Pattern类的matches方法
不使用先定义pattern,再使用matcher,直接使用matches
这里如果是判断一个字符串是否可以和一个正则表达式整体匹配,。只能判断整体 全部匹配
上面的只是找到了就可以。matcher.find()
Pattern.matches(regStr, content); //先写正则表达式串,再写字符串内容
使用pattern将字符串替换为别的字符串
// 使用patcher实现正则表达式的替换。此替换是返回新的字符串,原内容不变。
String regStr = "lgq";
String content = "agfkjisagjwlgqfggwseafgserffjiqaflgqglgqdajoifglgq";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
String newContent = matcher.replaceAll("刘广琦"); //replaceAll中参数为替换后的字符串
System.out.println("替换后的新字符串为" + newContent);
**反向引用 **
反向引用:
内部反向引用用 \\分组号
,外部反向引用用$分组号
String content = "(\\d)(\\d)\\2{2}\\1{1}" //匹配类似122211 155511即abbbaa
结巴去重案例
String content = "我...我要.....学学学学.......编程Java!";
String regStr = "\\.+";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
//1. 去掉所有的.
content = matcher.replaceAll("");
System.out.println(content);
//2. 去掉重复的字 使用(.)\\1+ 这样就是匹配至少出现2次的重复的字
//注意:由于正则表达式发生变化,所以需要重置matcher
pattern = Pattern.compile("(.)\\1+"); //分组的捕获内容记录到$1
matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println(matcher.group(0));
}
System.out.println("去掉.的字符:" + content);
//3. 使用反向引用$1来替换匹配到的内容
content = matcher.replaceAll("$1");
System.out.println("最后的字符为:" + content);
// 使用一条语法,去掉重复的字,
content = Pattern.compile("(.)\\1+").matcher(content).replaceAll("$1");
System.out.println("使用一条语法去掉重复的字为:" + content);.
//使用字符串的replaceAll直接进行正则表达式替换 将JDK1.4 JDK1.3替换为JDK
String content = "1s5a3fg1564 JDK1.3g srgJDK1.4";
content = content.replaceAll("JDK1(\\.3|\\.4)","JDK");
System.out.println(content);
//验证一个手机号,必须以138 139开头
// 直接使用字符串的matches方法,也是使用正则方法。
content = "13588889999";
System.out.println(content.matches("13(8|9)\\d{8}"));
//字符串的spilt方法
//要求按照# - ~ 数字来分割
content = "hello#abc-jack12smith~北京";
String[] spilt = content.split("#|-|~|\\d+");
for(String s:spilt)
System.out.println(s);
//判断邮箱是否OK合格
String content = "";
boolean ans = content.matches("[\\w-]+@([a-zA-Z]+\\.)+[a-zA-Z]+");
// 判断是否为整数或小数。
boolean ans = content.matches("^[-+]?([1-9]\\d*|0)(\\.\\d+)?$")
对象和类
引用类型的放在方法区中的常量池
成员变量 = 属性 = field(字段)
访问修饰符: 控制属性的访问范围,共4种:public, protected, 默认,private
Person p1 = new Person();
// p1是对象名,也称对象引用
// new Person()创建的对象空间(包括数据)才是真正的对象
// 对象的默认属性值和数组一样,char是\u0000 String是null,其他的都是0
//创建对象的两种方式
//1.先声明再创建
Cat cat; //此时并没有创建空间。
cat = new Cat();
// 2. 直接创建
Person p1 = new Person(); //在堆中开辟一个空间
此时堆中有一个空间,内有两个 空间是age和name,都是初始值0和null
赋值之后小明在常量区创建一个小明的引用类型
然后将p2指向p1。此时p1和p2指向堆中同一片空间
内存分配机制:堆栈
方法的使用原理
** 编写类AA 有一个方法,判断一个数是奇数odd还是偶数even。返回boolean**
//
import java.util.Scanner;
public class MethodExercise01 {
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
AA a = new AA();
System.out.println(num + "是否是?" + a.isEven(num));
scanner.close();
}
}
class AA {
public boolean isEven(int n){
if(n%2==0)
return true;
return false;
}
}
要写测试类,就是写main函数的类!!!!
IDEA快捷键
- 删除当前行:
删除所选行:Ctrl + d
- 复制当前行:
克隆行:Ctrl + Alt + 向下光标
- 补全代码:
Alt + /
- 添加注释和取消注释:
Ctrl + /
第一次是添加注释,第二次是取消注释 - 自动导入包:
Alt + Enter
- 重新格式化代码:
Ctrl + Alt + L
- 生成构造器:
Alt + insert
选择构造函数 - 查看一个类的层级关系:
Ctrl + h
- 迭代器for遍历:
I
- 迭代器itertor遍历:
itit
new Scanner(System.in).var => Scanner scanner = new Scanner(System.in);
- 提示快捷键:
Ctrl + Alt + T
try-catch什么的。 Ctrl+Shift+加号(回退左边的), 减号
: 展开或折叠方法体
import java.util.Arrays;
Arrays.sort(a); // 库函数对数组直接排序
访问修饰符 modifer:
类只有public和默认可以修饰
封装步骤:
- 将属性私有化private,保证除了本类,其他类无法直接访问修改该属性
- 设置set方法用于修改属性;设置get方法用于获取属性
继承细节:
- 子类必须调用父类的构造器来实现父类的初始化。(在子类的构造器中会默认调用父类的无参构造器(如果不显示调用父类的构造器的话))
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会调用父类的构造器。如果父类没有提供无参构造器,则不惜在子类的构造器中用super()去指定使用父类的那个构造器完成对父类的初始化工作。否则编译不会通过
- 如果希望指定调用父类的某个构造器,则显式的调用一下:super()
- super在使用时,需要放在构造器的第一行
- super()和this()都只能够放在构造器的第一行,因此这两个方法不能共存于一个构造器
- 访问父类的属性方法(private除外):super.属性名
- 调用子类的方法:
this.cal()
(这种方法等价于cal()
).调用父类的方法:super.cal()
this()
方法从从本类开始一直往父类找,并不是只找本类。this方法也可以访问到父类的变量和方法,子类没有就往父类找- this()从本类开始往父类找一直找到没有父类为止,super() 从父类开始往上找,一直找到没有父类为止
class father{
String name = "jack";
father(String s){
this.name = name;
}
}
class son extends father{
String name = "Rose";
son(String s){
super(s);
}
}
main中:
son("John");
这个方法是将John赋值给father类中的name而不是son类中的name。
class B{
B(){
// 无访问修饰符的变量。
this("ABC"); //调用B(String str)方法
System.out.println("b");
}
B(String str){
System.out.println("b name" + str);
}
}
方法重写Override
- 就是子类和父类的方法名都一样,就叫重写了。
- 子类的方法的参数,方法名称 要和父类方法的参数,方法名称完全一致。
- 子类方法的返回类型和父类方法返回类型一样,或者子类方法的返回类型是父类返回类型的子类。(父类是Object,子类可以是String)
- 子类方法不能缩小父类方法的访问权限。(保证先访问到子类)(public > protected > default > private)
如何继承父类的属性? 在子类中使用?
public Student(String name, int age, int id, int score){
super(name, age); //继承父类的name, age。 这里会调用父类构造器 父类要有这样的构造器
this.id = id;
this.score = score;
}
子类中如何调用父类的属性或方法,直接使用?
如果父类的属性是private的,只能使用get方法。如果要重写父类的方法,可在子类里直接调用父类的方法:super.方法()
多态
- 一个对象的编译类型和运行类型可以不一致。编译类型为=的左边,运行类型为等好的右边。
- 编译类型在定义对象时就确定了,不能改变。
- 运行类型是可以变化的
- 父类的对象引用可以指向子类对象
Animal animal = new Dog()
Animal animal = new Dog(); // 编译类型是animal,运行类型是Dog
animal = new Cat(); // 将运行类型改为Cat
- 多态的前提是:两个对象(类)存在继承关系
1.多态的向上转型(父类的引用指向子类的对象)
父类类型 引用名 = new 子类类型();
- 可以调用父类中的所有成员+方法(须遵守访问权限),不能调用子类中特有成员+方法,可以调用子类重写父类的方法
- 运行方法或属性时,从当前子类到父类依次找方法或属性
2.多态的向下转型
把指向子类对象的父类引用,转化为指向子类对象的子类引用。
将父类的对象引用转为子类的对象引用。如果这个引用不是父类类型或其子类的,则无法向下转型。向下转型时要判断引用是否为父类类型或其子类的
- 语法:
子类类型 引用名 = (子类类型) 父类引用
- 当向下转型后,可以调用子类类型中的所有成员。
Cat cat = (Cat) animal; // 将父类的引用转换为子类的
此时animal引用是指向cat,定义新的引用cat也是指向cat。此时有两个引用都指向Cat类。区别在于animal引用不能调用子类特有成员,cat引用可以调用子类所有成员。 - 只能强转父类的引用,不能强转父类的对象(对象是什么不可改变,但是引用是可以转为子类的)
- 要求父类的引用必须指向的是当前目标类型的对象。
// 错误示例
Animal animal = new Cat();
Cat cat = (Cat) animal; // 向下转型,此时cat和animal都指向Cat类
Dog dog = (Dog) animal // 报错。animal指向Cat类,不能转为Dog类。
Person person = new Student(); //向上转型,
Student student = (Student) person; //向下转型
//下面是错误示例:
Person person = new Person();
Student student = (Student) person; //向下转型 错误!
// person本来是指向父类对象的父类引用而不是指向子类对象的父类引用,所以无法向下转型。
属性没有重写之说,属性的值看的编译类型 即看= 左边的
instanceOf 用于判断对象的运行类型是否为XX类型或XX类型的子类型.
可以直接用getClass()方法查看对象的运行类型
属性只看编译类型,方法要看运行类型。从子类到父类依次去找。
Java的动态绑定机制★★★★★
- 当调用对象方法时,该方法会和该对象的内存地址/运行类型绑定。(从子类调用方法,调用到父类方法时,又嵌套了子类父类都有的方法时,此时会调用子类方法而不是父类方法。)
- 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用。
多态的应用
多态数组
数组的定义为父类类型,里面保存的元素类型为子类类型
父类类型的引用可以指向子类类型的对象
// 要调用子类中不同的方法的实现:向下转型
// 如果仅调用子类中相同方法,直接调用就可。向上转型在调用方法和属性时从子类开始找。
Person[] persons = new Person[3];
persons[0] = new Person("龙岗区", 22);
persons[1] = new Student("我需要", 22, 20, 100);
persons[2] = new Teacher("和视频", 40, 10000000);
for(int i=0; i< persons.length; i++){
if(persons[i] instanceof Student){
Student student = (Student) persons[i]; // 向下转型 本来是向上转型的,无法使用子类特有成员方法。 所以要向下转型来使用子类特有的成员方法。
System.out.println(student.learn());
} else if(persons[i] instanceof Teacher){
Teacher teacher = (Teacher) persons[i];
System.out.println(teacher.teach());
}else if(persons[i] instanceof Person){
System.out.println(persons[i].say());
}
}
多态参数
向下转型并访问子类特有方法:
Employee e = new Employee();
// 1. 先转型,在访问
Executive manager = (Executive) e;
return manager.manage();
// 2. 转型和访问一起实现
((CEmployee) e).work(); //(CEmployee) e 是向下转型
Object类中常用方法
equals()
hashCode() // 返回该对象的哈希码值
getClass()
equals
==和equals的区别:
- ==:既可以判断基本类型,也可以判断引用类型。如果判断基本类型,判断的是值是否相等。10 和10.0是相等的;如果判断引用类型,判断的是地址是否相等,即判断是否为同一个对象
- equals:只能判断引用类型。默认判断地址是否相等。子类中往往重写该方法,用于判断值是否相等。如Integer,String。
练习1:
// 重写Person类继承的Object类中的方法:
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if(obj instanceof Person){
Person p = (Person) obj; //将Object类向下转型到Person类
return p.name.equals(this.name)&&p.age==this.age;
}
return false;
}
hashCode
- 提高具有哈希结构容器的效率
- 两个引用,如果指向的是同一个对象,则哈希值完全一样
- 两个引用,如果指向的是不同对象,则哈希值是不一样的。
- 哈希值主要根据地址号来的,但不完全等价。
- 后面在集合中,如果用到hashCode,则会重写。
toString
- 返回该对象的字符串表示。默认返回全类名+@+哈希值的十六进制。 全类名=包名+类名
- 直接输出一个对象,toString会被默认调用。sout(Person) <->sout(Person.toString())
将int, double转为toString
int a = 5;
double b = 0.5;
sout(Integer.toString(a));
sout(Double.toString(b));
断点调试
java对象数组在使用时,要每个重新赋值。
houses[idx] = new House(); // 每一个数组的元素都要重新设置。
类变量
类变量,也叫静态变量,用static修饰。
该变量最大的特点就是会被该类所有的对象实例共享
类变量可以通过类名.类变量名
或对象名.类变量名
来访问
静态方法(类方法)
当方法中不涉及任何与对象相关的成员时,则可以将方法设计成静态方法,提高开发效率。
- 类方法中不允许使用和对象有关的关键字,如this和super。普通成员方法可以。
- 静态方法中只能访问 静态变量 或 静态方法。
- 普通成员方法中,即可访问静态成员,也可访问非静态成员。
- 静态方法只能访问静态的成员,非静态的方法可以访问静态成员+非静态成员
代码块
代码块又称初始化块,属于类中的成员,类似于方法,封装于方法体内,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,在加载类或创建对象时隐式调用。
// 语法:
[修饰符]
{
}; //;可以省略
//eg:
{
System.out.println("电脑屏幕打开");
System.out.println("广告开始");
System.out.println("电影正式开始");
}
注意:
- 修饰符可选,要写也只能写static。
- 可以写可以省略
- 代码块分为两类:静态代码块(使用static修饰),普通代码块(不使用static修饰)
优点:
- 相当于另一种形式的构造器(对构造器的补充机制),可以做初始化。
- 如果多个构造器中都有重复语句,可以抽取到初始化块中,提高重写性。
细节:
- static代码块作用就是对类进行初始化,而且它随着类的加载而执行,且只会执行一次;如果是普通代码块(无static修饰)则每创建一个对象,就执行。
- 静态代码块只能调用静态成员(属性和方法),普通代码块可以调用任意成员。
类加载时做的事情:
- 调用构造器
- 调用静态变量,静态方法。
- 调用代码块(相当于构造器的一部分)
- 类什么时候被加载:
- 创建对象实例时(new)
- 创建子类对象实例时,父类也会被加载
- 使用类的静态成员时(静态属性/静态方法)
- 普通的代码块在创建对象实例时,会被隐式的调用。被创建一次则调用一次。
小结:
- static代码块在类加载时执行,只会执行一次。
- 普通代码块在创建对象时调用,创建一次调用一次
创建一个对象时,在一个类中的调用顺序是:(重点,难点)
① 调用静态代码块和静态属性初始化。(多个静态的按定义的顺序调用)
② 调用普通代码块和普通属性的初始化。(多个普通的按定义的顺序调用)
③ 调用构造方法。
注: 上面的静态方法和普通方法在类中调用该方法时才会使用。
父类的初始化
构造器的最前面其实隐含了super()和调用普通代码块。
class A{
public A(){
// 这里有隐藏的执行要求
// 1.super()
// 2.调用普通初始代码块
// 3. 构造器的内容。
}
}
创建一个子类对象(继承关系时),其静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法调用顺序:
① 父类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
② 子类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
③ 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④ 父类的构造方法
⑤ 子类的的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥ 子类的构造方法
单例设计模式
单例就是在整个运行过程中,某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例设计模式有两种方式:1. 饿汉式 2.懒汉
步骤【饿汉式】:
- 构造器私有化(防止直接new)
- 类的内部创建静态对象
- 向外暴露一个静态的公共方法。
注:因为要在不创建对象实例的前提下得到一个对象实例,所以必须new静态对象,提供静态方法。
class Cat {
// 饿汉式
private String name;
private static Cat cat = new Cat("Tom"); //在类中创建静态private对象
// 构造器私有化
private Cat(String name) {
this.name = name;
}
public static Cat getInstance(){
// 静态方法返回对象
return cat;
}
}
class Cat {
// 懒汉式
private String name;
private static Cat cat; //创建对象引用,但不创建
private Cat(String name) {
this.name = name;
}
public static Cat getInstance(){
if(cat == null)
cat = new Cat("Tom"); //在需要得到对象时才去引用
return Cat;
}
}
懒汉式VS饿汉式
- 两者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建。
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
- 饿汉式存在良妃资源的可能。因为如果没有使用对象实例,则饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存这个问题。
final关键字
可以修饰类,属性,方法和局部变量。
- 不希望类被继承时,可以用final修饰
- 不希望父类的方法被子类覆盖/重写(override)时,可以用final关键字修饰。
- 当不希望类的属性的值被修改,可以用final修饰。
- 当不希望某个局部变量被修改,可以使用final修饰。
final使用注意事项
- final修饰爹属性又称常量,一般用XX_XX_XX命名
- final修饰的属性在定义时,必须赋初值,且不可再更改。赋值可在以下位置:
- 定义时:
public final double MAX_RATE = 0.08
- 在构造器中
- 在代码块中
- 定义时:
- 如果final修饰的属性是静态的,则初始化的位置只能是:(不可在构造器中。)
- 定义时
- 在静态代码块中。(静态代码块就是在{}前面加上static)
- final类不能继承,但可实例化对象
- 如果类不是final类但含有final方法,则该方法虽然不能重写,但是可以被继承。
- 如果类是final类,则没必要将方法修饰成final方法。
- final不能修饰构造方法
- final和static往往搭配使用,效率更高。不会导致类加载。
抽象类
当父类的某些方法需要声明但有不确定如何实现时,可以将其声明为抽象方法,则这个类就是抽象类。
// 抽象类示例:
abstract public void eat();
-
用abstract关键字来修饰一个类时,这个类就叫抽象类。
访问修饰符 abstract 类名{ }
-
用abstract关键字来修饰一个方法时,这个方法就是抽象方法。
// 抽象方法没有方法体 访问修饰符 abstract 返回类型 方法名(参数列表);
-
抽象类的价值更多在于设计,是设计者设计好后,让子类继承并实现抽象类。
-
抽象类不能被实例化。
-
抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法。
-
一旦类包含了abstract方法,则这个类必须声明为abstract。
-
abstract只能修饰类和方法,不能修饰其他的。
-
如果一个类继承了抽象类,则他必须实现抽象类的所有抽象方法,除非他自己也声明为abstract类。
-
抽象方法不能用private,final,static修饰。
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把方法写出来。
interface 接口名{
// 属性
// 抽象方法
}
class 类名 implements 接口{
// 自己属性
// 自己方法
// 必须实现的接口的抽象方法
}
小结: 接口是更抽象的抽象的类。抽象类里的方法可以有方法体,接口里的所有方法都没有方法体。接口体现了程序设计的多态和高内聚低耦合的思想。
- 接口不能被实例化。
- 接口中所有的方法是public方法,接口中抽象方法可以不用abstract修饰。
- 一个普通类实现接口,必须将该接口的所有方法都实现。
- 抽象类实现接口,可以不用实现接口的方法。
- 一个类可以同时实现多个接口
- 接口中的属性只能是final,而且是
public static final
修饰符。 - 接口中属性的访问形式:
接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口。
- 接口的修饰符只能是public 和 默认,这点于类的修饰符是一致的。
- 接口类型可以指向实现该接口的类。
Phone phone = new UsbInterface()
(多态)
接口vs继承
- 子类继承了父类,就自动拥有了父类的功能。
- 如果子类需要拓展功能,可以通过实现接口方式扩展。
- 可以理解,实现接口 是 对java 单继承机制 的一种补充。
- 继承的价值在于:解决代码的复用性和可维护性。接口的价值在于:设计好各种规范(方法),让其他类去实现这些方法。
- 接口比继承更灵活。
内部类
一个类的的内部又完整的嵌套了另一个类结构被嵌套的类被称为内部类inner class,乞讨其他类的类称为外部类outer class。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
如果定义在局部位置(方法中/代码块):1. 局部内部类 2. 匿名内部类。
定义在成员位置:1. 成员内部类 2. 静态内部类。
// 基本语法:
class Outer{
class Inner{
}
}
class Other{
}
内部类的分类:
定义在外部类局部位置上(通常在方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名)
定义在外部类的成员位置上:
- 成员内部类(没有static修饰)
- 静态内部类(使用static修饰)
局部内部类(有类名)
- 定义在外部类的局部位置,比如方法或代码块中,且有类名。
- 不能添加访问修饰符,只可以用final修饰。地位相当于局部变量。
- 作用域:仅仅在定义它的方法或代码块中
- 局部内部类-访问-外部类的成员:【直接访问】
- 外部类-访问-局部内部类的成员:【在主类体内先创建局部内部类对象在调用局部内部类方法(非常扯淡)】
- 外部其他类不能访问局部内部类。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想要访问外部类的成员,可使用【外部类名.this.成员】
class A{
public void fun(){
class B{
private final String name = "刘广琦";
public void show(){
System.out.println("局部内部类B的name=" + name);
System.out.println("外部类A的name=" + A.this.name);
}
}
B b = new B(); // 在主类体内先创建局部内部类对象
b.show(); // 调用局部内部类方法
}
}
匿名内部类(无类名)★★
匿名内部类和接口:通过创建一个实现接口的匿名类的实例,从而间接地使用接口
- 本质是类 2. 内部类 3. 该类没有名字 4. 同时还是一个对象
匿名内部类通常用于重写已有的类或接口的方法。
new 类或接口(参数列表) {
};
- 可以调用匿名内部类的方法。
- 可以直接访问外部类的所有成员,包括私有的。匿名内部类访问外部类成员:【直接访问】
- 外部其他类不能访问匿名内部类【因为匿名内部类地位是一个局部变量】
- 不能添加访问修饰符。因为他的地位就是局部变量。
- 作用域:仅仅在定义他的方法或代码块中。
- 如果外部类和匿名内部类的成员重名时,访问遵循就近原则。如果想访问外部类的成员,可以使用【外部类名.this.成员】
匿名内部类当作参数
// 例1
// 示匿名内部类
public class AnonymousClass {
public static void main(String[] args) {
// 直接使用匿名内部类。
// 匿名内部类可以当作实参直接传递,简介高效
f1(new IL(){
@Override
public void show() {
System.out.println("这是一副名画~~~~");
}
});
// 传统方法 先写一个类实现IL接口,再用这个类
f1(new Picture());
}
//静态方法 形参是接口类型
public static void f1(IL il){
il.show();
}
}
interface IL {
void show();
}
//不使用匿名内部类:类->实现 IL => 编程领域 (硬编码:专门写一个类来实现某个仅使用一次的功能)
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画 XX...");
}
}
// 例2
public class Telephone {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
/*
* 传递的是实现了Bell接口的匿名内部类
* 重写了ring方法,即接口的ring方法
* alarmclock的参数Bell bell的编译类型是最初的Bell接口
* 运行类型是新实现的匿名内部类
*/
cellphone.alarmclock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了");
}
});
}
}
public class Cellphone{
public void alarmclock(Bell bell){
bell.ring();
}
}
interface Bell{
public void ring();
// 例3
package enum_;
public class homework04{
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
cellphone.testWork(new Calculator() {
//重写Calculator()接口,变成类
@Override
public double work(double a, double b) {
return a + b;
}
}, 10, 20);
}
}
interface Calculator {
public double work(double a, double b);
}
class Cellphone{
public void testWork(Calculator calculator, double a, double b){
double res = calculator.work(a, b);
System.out.println("计算后的结果是" + res);
}
}
成员内部类
成员内部类是定义在外部类的成员位置,且没有static修饰。
-
可以直接访问外部类的所有成员,包含私有的。
-
可以添加任意访问修饰符(public, protected, 默认, private),因为它的地位就是一个成员
-
作用域:整个类。
-
成员内部类访问外部类成员:【直接访问】
-
外部类访问成员内部类:【先创建对象,在访问】
-
外部其他类访问成员内部类:【2种方式】
//外部其他类使用成员内部类的方法 // 法一 先创建父类,再dot 子类创建子类 Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.hi(); // 调用hi方法 // 法二 在外部类中编写一个方法返回内部类对象 Outer.Inner inner1 = outer.getInstance(); inner1.hi(); // 调用hi方法 class Outer{ class Inner{ // 成员内部类 public void hi(){ System.out.println("hi()方法..."); } } //内部类 public Inner getInstance(){ return new Inner(); }
package enum_;
public class homework07 {
public static void main(String[] args) {
Car car = new Car(39);
Car car1 = new Car(41);
Car car2 = new Car(-14);
Car.Air air = car.new Air(); // 直接创建子类对象
Car.Air air1 = car1.new Air();
car.getAir().flow(); //使用方法返回一个内部类Air对象
car1.getAir().flow();
car2.getAir().flow();
}
}
class Car{
private int temperature;
public Car(int temperature) {
this.temperature = temperature;
}
public int getTemperature() {
return temperature;
}
public void setTemperature(int temperature) {
this.temperature = temperature;
}
class Air{
public void flow(){
if(temperature>40){
System.out.println("吹冷气");
}
else if(temperature < 0){
System.out.println("吹暖气");
}
else{
System.out.println("关闭空调");
}
}
}
// 提供一个方法,返回Air对象
public Air getAir(){
return new Air();
}
}
- 如果外部类和成员内部类的成员重名时,内部类访问遵循就近原则,如果想访问外部类的成员,可以使用【外部类名.this.成员名】
静态内部类
静态内部类是定义在外部类的成员位置,且有static修饰。
-
可以直接访问外部类的所有静态成员,包括私有的。但不能访问非静态成员。
-
可以添加任意访问修饰符(public,protected,默认,private),因为其地位就是一个成员
-
作用域:整个类
-
静态内部类访问外部类(只能访问静态):【直接访问】
-
外部类访问静态内部类:【先创建对象,在访问】
-
外部其他类访问静态内部类:
// 方法一 因为静态内部类,所以可以通过类名直接访问,不用先创建外部类实例 Outer.Inner inner = new Outer.Inner(); inner.say(); //调用静态内部类say方法 // 方法2 编写一个方法,返回静态内部类的对象实例。 Outer outer = new Outer(); //创建外部类对象实例 Outer.Inner inner = outer.getInner(); class Outer{ static class Inner{ //... } public Inner getInner() { return new Inner();} }
-
如果外部类和静态内部类的成员重名时,静态内部类访问时,遵循就近原则,如果想访问外部类的成员,使用【外部类名.成员去访问】
枚举类型的使用
多个枚举类对象用逗号分隔,且要将枚举类对象放在最前面
enum常用方法应用实例:
- toString:Enum 类已经重写过了,返回的是当前对象
名,子类可以重写该方法,用于返回对象的属性信息 - name:返回当前对象名(常量名),子类中不能重写
- ordinal:返回当前对象的位置号,默认从 0 开始
- values:返回当前枚举类中所有的常量
- valueOf:将字符串转换成枚举对象,要求字符串必须
为已有的常量名,否则报异常! - compareTo:比较两个枚举常量,比较的就是编号!
// 示例
enum Season{
SPRING("春天","温暖"), //等价于public static final Season SPRING = new Season("春天", "温暖")
SUMMER("夏天", "炎热"),
AUTUMN("秋天","凉爽"),
WINTER("冬天","寒冷");
private String name;
private String desc;
private Season(){
}
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
//举例2
public class Enumeration01 {
public static void main(String[] args) {
Week[] weeks = Week.values();
for(Week week : weeks){
// 增强for循环
System.out.println(week); //这里输出的实际为toString方法中的返回值
}
}
}
enum Week{
MONDAY("星期一"), TUESDAY("星期二"),
WEDNESDAY("星期三"), THURSDAY("星期四"),
FRIDAY("星期五"), SATURDAY("星期六"), SUNDAY("星期日");
private String name;
private Week(String name){
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
// 使用switch匹配枚举类对象
package enum_;
public class homework08 {
public static void main(String[] args) {
//枚举值在switch中的使用
Color green = Color.GREEN;
green.show();
switch (green){
case RED:
System.out.println("匹配到红色");
break;
case BLUE:
System.out.println("匹配到蓝色");
// ...
}
}
}
enum Color implements Interface{
RED(255,0,0),
BLUE(0,0,255),
BLACK(0,0,0),
YELLOW(255,255,0),
GREEN(0,255,0);
private int redValue;
private int greenValue;
private int blueValue;
Color(int redValue, int greenValue, int blueValue) {
this.redValue = redValue;
this.greenValue = greenValue;
this.blueValue = blueValue;
}
@Override
public void show() {
System.out.println("redValue=" + redValue);
System.out.println("greenValue=" + greenValue);
System.out.println("blueValue=" + blueValue);
}
}
interface Interface {
public void show();
}
Annotation注解
@Override 重写
@interface 表示一个注解类
@Target 修饰注解的注解,称为元注解。
@Deprecated 用于表示某个程序元素已过时。修饰某个元素
@SuppressWarnings: 抑制编译器警告
JDK 的元 Annotation 元注解
- Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间。 - Target // 指定注解可以在哪些地方使用
用于指定被修饰的Annotation能用于修饰哪些程序元素。 - Documented //指定该注解是否会在 javadoc 体现
用于指定被该元Annotation修饰的Annotation类被javadoc工区提取成文档。即在生成文档时,可以看到该注解。
说明:定义为Documented的注解必须设置Retention值为RUNTIME. - Inherited //子类会继承父类注解(使用较少)
被他习俗hi的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。
异常
异常的第一个案例
package exception_;
public class Exception01 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
try {
int res = num1 / num2;
} catch (Exception e) {
System.out.println("出现异常的原因=" + e.getMessage());
}
System.out.println("程序继续运行");
}
}
Integer.parseInt(String s) 将字符串s转为int类型
try-catch
public static void main(String[] args) {
String str = "132456l";
try {
int a = Integer.parseInt(str);
System.out
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!