面向对象思想(Object Oriented Programming,OOP)是一种软件开发方法,将实体抽象成类,它强调将程序看作是一组对象的集合,每个对象都有自己的状态和行为,并且可以与其他对象进行交互。这种思想的核心是封装、继承和多态。
重写: 也叫覆盖,发生在子类或者接口当中,方法名、参数列表、返回值类型要保持一致,或者返回值是其子类;访问修饰符范围必须要比父类大或者一致,抛出的异常类型也不能比父类更多
重载: 发生在同一类当中,方法名要保持一致,参数列表要不一致(数量、类型、顺序),返回值可以是任何类型,也可以具有不同的访问修饰符,也可以抛出不同的异常类型
方法重载的作用:
构造方法: 用于创建和实例化对象
构造方法具有以下特点:
构造方法与类名相同,没有返回值类型(连void都没有)
构造方法可以被重载,可以定义多个不同参数列表的构造方法;但注意不能被重写!不能被继承!子类只能通过super()方法来调用
如果没有定义任何构造方法,编译器会自动生成一个默认构造方法(public 类名(){}),该方法不接受任何参数并且什么也不做
如果定义了至少一个构造方法,则默认构造方法不再生成,需要显式定义
this关键字指代本类中的属性或方法,但不能放在静态方法中
new一个对象后,可以.方法也可以.属性
public Account(){
//使用this调用本类重载的其他构造方法
this("000","123456",0.0);
}
实例块和静态块的区别:
public class TestBlock {private int x;{ //每次调用构造方法前自动调用System.out.println("实例块");}static{ //类加载时被调用一次,仅一次,与是否创建对象无关System.out.println("静态块");}public static void main(String[] args) {new TestBlock();new TestBlock();}
}
//运行结果是:
//静态块
//实例块
//实例块
内部类:
Java内部类是定义在另一个类中的类,它包含在外部类的作用域内。使用内部类可以访问外部类的成员变量和方法,同时也可以被外部类访问。
Java内部类的语法如下:
class OuterClass {// 外部类代码class InnerClass {// 内部类代码}
}
要使用内部类,可以利用外部类的实例来创建内部类的对象,例如:
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = w InnerClass();
创建内部类实例的时候需要使用外部类的实例进行调用,Java内部类的主要作用是实现一些隐藏、私有化、封装化等应用场景。例如,某些类只需要在外部类中被使用,而不需要在整个应用程序中暴露,这时可以将该类定义为内部类。此外,内部类中还可以声明静态成员和方法
标识符的命名约定:(驼峰式命名)
问号表达式:
三元运算符(问号运算符)的格式:
//test1是一个布尔表达式,如果值为true,则取test2的值,为false则取test3的值
test1 ? test2 : test3;
权限访问修饰符,从大到小为:
封装性:
在Java中,封装的具体实现方式是使用访问修饰符对成员变量和方法进行限制,同时提供公共的方法(Getter/Setter)用于访问和修改成员变量。以下是一个简单的示例:
public class Student {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {if(age >= 0 && age <= 120) {this.age = age;} else {System.out.println("Invalid age value!");}}
}
在上面的示例中,Student类有两个私有的成员变量name和age,分别表示学生的姓名和年龄,通过提供公共的Getter/Setter方法对外暴露这些成员变量。在Getter/Setter方法中,可以加入一些逻辑判断和约束条件,确保内部数据的安全性。比如上面的示例中,setAge方法中限制了age值必须在0到120之间。这样就实现了对数据的封装,在外部只能通过公共的接口访问和修改内部数据,确保了代码的可维护性和安全性。
继承 是指一个对象可以从另外一个对象继承其属性和方法,注意被final修饰的属性和方法不能被继承或者覆盖。通过继承,可以减少代码的重复性,提高代码的复用性和可维护性
多态 是指同一个方法可以在不同的对象上产生不同的行为。通过多态,可以实现代码的灵活性和可扩展性。多态是面向对象思想的核心之一,它使得程序可以根据不同的情况做出不同的响应
多态的具体体现:
class Animal {public void makeSound() {System.out.println("动物发出声音");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("狗发出汪汪声");}
}class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("猫发出喵喵声");}
}public class PolymorphismExample {public static void main(String[] args) {Animal animal1 = new Dog();Animal animal2 = new Cat();animal1.makeSound(); // 调用的是子类Dog的makeSound方法animal2.makeSound(); // 调用的是子类Cat的makeSound方法}
}
代码解释:
在这个例子中,Animal是父类,而Dog和Cat是Animal的子类。PolymorphismExample类中,我们创建了一个Animal类型的animal1对象,但实际上将其赋值为Dog类的一个实例。同样地,我们创建了另一个Animal类型的animal2对象,但将其赋值为Cat类的一个实例。
当调用animal1.makeSound()时,由于animal1引用的实际对象是Dog类的实例,因此会调用Dog类中的makeSound方法,并输出"狗发出汪汪声"。
当调用animal2.makeSound()时,由于animal2引用的实际对象是Cat类的实例,因此会调用Cat类中的makeSound方法,并输出"猫发出喵喵声"。
这就是Java多态性的体现,通过父类引用指向不同子类对象,根据实际对象的类型来自动调用相应的方法实现。
数据类型精度从小到大的顺序为:
byte < short < int < long < float < double<boolean<char
int a = 123;
double b = a; // 隐式类型转换
System.out.println(a); // 输出:123
System.out.println(b); // 输出:123.0
double a = 123.456;
int b = (int) a; // 显式类型转换
System.out.println(a); // 输出:123.456
System.out.println(b); // 输出:123
向上转型
作用是:提高程序的扩展性
class Animal{abstract void eat();
}
class Cat extends Animal{void look() {System.out.println("看家");}} Animal x=new Cat() //向上造型,Cat对象提升到Animal对象x.eat() //只能使用父类中的方法x.look() //报错!不能使用子类中的方法
向下转型
作用是:为了使用子类中的特有方法
class Animal{abstract void eat();
}
class Cat extends Animal{void look() {System.out.println("看家");}} Animal x=new Cat()Cat m=(Cat)x; //向下转型m.eat() ;m.look();//子父类中的方法都可以使用
求类型为字符串String的数组长度时有括号length(),而求整型int的数组长度时没有括号length
数组迭代的两种方式:
for(int i =0;i<a.length;i++) {System.out.println(a[i]);}
//for(数据元素的类型 临时变量的名称 : 数组的名字){}for(int x:a) {System.out.println(x);}
数组的copy:
数组拷贝的方法在System类中
//System.arraycopy(始, 始下标 , 终, 终下标, 允许覆盖位数);int a1[]={1,2,3};int b2[]={4,5,6,};System.arraycopy(a1, 1, b2, 1, 2);for (int i : b2) {System.out.println(i);}//输出结果为:4 2 3
移位运算符(先转换为二进制再进行运算):<< 、>>(有符号右移)、>>>(无符号右移),左移一位相当于×2,右移一位相当于÷2,移两位就乘或除4(效率比用乘除号高)
int a =8,c;
c = a>>2;
//原来为 0000 1000
//移动后为 0000 0010
逻辑运算符
&&和&、||和| 的区别:
Java会出现空指针异常(NullPointerException)是因为代码中使用了一个空对象,而这个空对象没有进行初始化或者已经被释放了。
在Java中,每个对象变量都存储着对对象的引用,如果该引用值为null,就表示该变量不指向任何对象。当我们调用一个空对象的方法或属性时,在编译时并不会报错,但在运行时程序会抛出空指针异常。
例如,下面代码中的str变量未初始化,它的值为null,如果我们尝试调用它的length()方法,就会抛出空指针异常:
String str = null;
int len = str.length(); // 这里会抛出空指针异常
这个异常在Java编程中比较常见,因为我们在代码中很容易忽略一个对象是否为空,而直接进行属性和方法的调用。因此,我们在写Java程序时,应该始终意识到对象可能为空,并编写相应的代码来避免空指针异常的发生。
解决办法: 可以在使用对象之前先进行非空判断,以确保对象不为空;或者在创建对象时进行初始化,以避免变量的值为空。另外,也可以使用try-catch语句来捕获空指针异常,从而保证程序的正常运行。
以下例子不会出现空指针异常:
class Person {static void sayHello() {System.out.println("HelloWorld!");}
}
public class Example {public static void main(String[] args) {((Person) null).sayHello();}
}
代码解释: 由于静态方法sayHello()是与类关联而不是对象关联的,所以它可以直接通过类名调用,而不依赖于具体的对象实例。在这段代码中,尽管 (Person) null 强制转换的结果是一个空对象引用,但由于 sayHello() 方法是静态的,它不需要访问或操作任何对象的特定状态,因此不会导致空指针异常。相反,它只是简单地在标准输出中打印了 “HelloWorld!” 字符串。需要注意的是,在其他情况下,如果调用一个非静态方法时使用了空对象引用,就会抛出空指针异常。这是因为非静态方法需要一个有效的对象实例来执行,而空对象引用没有与之关联的对象,无法执行非静态方法。
需要注意的是: final 修饰的变量可以是基本类型或引用类型。对于基本类型,一旦被赋值后就不能再修改其值;对于引用类型,一旦被赋值后就不能再指向其他对象,但是该对象的属性值是可以被修改的。修饰的变量可以是全局变量或局部变量,全局变量需要在声明时赋值,而局部变量则可以在方法中任何位置赋值
final关键字的使用场景如下:
当一个类不希望被继承时
当一个方法不希望被子类重写时
当一个变量在定义后不希望被修改时
当一个变量在多线程环境下需要保证线程安全时,可以将该变量声明为final,因为final变量在多线程环境下是不可变的,所以不会出现线程安全问题
属性都有默认值:整型为0,浮点型是0.0,布尔型为false,字符型或引用类型都是null,但局部变量不被自动初始化,必须手动初始化。
关联关系也是“has”的关系
分为单向和双向关联(一个类做为另一个类的属性类型存在)
//单向关联public class Phone {private Person per;}//如果下面的也有那就是双向关联public class Person {private Phone phone;}
还分为一对一和一对多关联
解决一对多可以使用集合或数组:
//集合public class Classes{ }public class Student{private List Classess;}//数组public class Classes{ }public class Student{private Classes[] Classess;}
如果两个互相关联的类中有整体和部分的关系,关联关系分为: 聚合和组合,主要区别在于生命周期不同。
聚合:创建Team对象时Player对象可以不创建,当Player 对象销毁时Team还没销毁
public class Team{private Player player;//运动员}public class Player{}
组合(绑定):创建Team对象的时Player类同样创建, Team对象销毁时,Player对象也销毁
public class Team{private Player p=new Player();//队员}class Player{}
依赖关系是“use”的关系:指一个类A使用到了另一个类B
表现为类B作为参数被类A在某个method方法中使用,例如:
public class Person {public void travel(Bus bus){}}
super关键字
public class A extends B{A(){super(); //调用父类的构造方法,一定要放在方法的首个语句}
}
如果类中有抽象方法,则该类必须定义成抽象类,但抽象类中不一定有抽象方法,抽象类不能被实例化
抽象类只能用作基类(父类),表示的是一种继承关系。继承抽象类的非抽象类必须实现其中的所有抽象方法,而已实现方法的参数、返回值要和抽象类中的方法一样。否则,该类也必须声明为抽象类。
public abstract void draw(); //没有花括号
抽象类的作用: 抽象类主要用来进行类型隐藏;也就是使用抽象的类型来编程,但是具体运行时就可以使用具体类型。能够在开发项目中创建扩展性很好的架构,优化程序。
编译(Compile):编译是将高级语言代码转换为计算机能够理解和执行的低级语言(如机器码或字节码)的过程。编译器会对源代码进行语法检查、语义分析和优化,最终生成可执行的目标代码或中间代码。在编译阶段,程序员可以发现并解决一些潜在的问题,如语法错误、类型错误等。编译后的代码可以在稍后的时间内重复运行,而无需重新编译。
运行(Run):运行是指执行已经编译好的程序的过程。在运行时,计算机会加载并执行编译生成的目标代码或中间代码。程序被加载到计算机内存中,按照指定的执行顺序逐行执行。在运行过程中,程序会与外部环境进行交互,接收输入并产生输出。运行阶段是将代码转化为实际结果、实现预期功能的过程。
用法:result = 对象名称 instanceof 类型
说明:如果对象是这个类型的一个实例,则 instanceof 运算符返回 true。如果对象不 是指定类的一个实例,或者对象是 null,则返回 false
示例:
public class Animal {// Animal类的定义
}
public class Dog extends Animal {// Dog类继承自Animal类
}
public class Cat extends Animal {// Cat类继承自Animal类
}
public class Example {public static void main(String[] args) {Animal animal = new Dog(); // 创建一个Dog对象并赋值给Animal类型的变量if (animal instanceof Dog) {System.out.println("animal是Dog类或其子类的实例");} else {System.out.println("animal不是Dog类或其子类的实例");}Animal anotherAnimal = new Cat(); // 创建一个Cat对象并赋值给Animal类型的变量if (anotherAnimal instanceof Dog) {System.out.println("anotherAnimal是Dog类或其子类的实例");} else {System.out.println("anotherAnimal不是Dog类或其子类的实例");}}
}
其输出结果为:
animal是Dog类或其子类的实例
anotherAnimal不是Dog类或其子类的实例
解释说明:在示例中,animal对象是Dog类型的实例,因此animal instanceof Dog的判断结果为true;而anotherAnimal对象是Cat类型的实例,所以anotherAnimal instanceof Dog的判断结果为false。通过使用instanceof关键字,可以在Java中方便地判断对象的类型和继承关系。
JDK是用于开发Java应用程序的工具包,而JRE是用于运行Java应用程序的运行时环境。JRE包含了Java虚拟机(JVM)和Java核心类库等运行时必需的组件,而JDK则在此基础上提供了开发所需的编译器和开发工具等
Object是所有类的根,包括数组
Object和Object[]之间的区别:
方法中的形参是Object类型时,任何类型的参数都可以传进去执行。
方法中形参是Object[]类型时,只有对象数组可以传入执行。
public static void arrayTest(Object[] obj){
}
public static void main(){int[] array=new int[4]; arrayTest(array)//错误出现,因为array不是对象型的// 换成 Array [] array=new Array[4]; 才对,其中Array是一个类,是一个对象型的
}
hashCode方法
获取对象的哈希码值,为16进制
equals方法与hashCode方法关系:
如果两个对象使用equals比较返回true,那么它们的hashCode值一定要相同
如果两个对象equals比较返回false,那么它们的hashCode值不一定不同
例题
以下代码会输出size:4
class RectObject {public int x;public int y;public RectObject(int x, int y) {this.x = x;this.y = y;}@Overridepublic int hashCode() {// TODO Auto-generated method stubreturn (int)System.nanoTime();}@Overridepublic boolean equals(Object obj) {return false;}
}
public class Example {public static void main(String[] args) {HashSet<RectObject> set = new HashSet<RectObject>();RectObject r1 = new RectObject(3, 3);RectObject r2 = new RectObject(5, 5);RectObject r3 = new RectObject(3, 3);set.add(r1);set.add(r2);set.add(r3);set.add(r1);System.out.println("size:" + set.size());}
}
解释说明: HashSet不会将完全相同的元素重复添加进去,RectObject类重写了hashCode()方法,使其返回每次调用都会产生不同的哈希码。这样每个RectObject对象的哈希码都不同。当向HashSet中添加元素时,它首先会通过hashCode()方法确定元素应该存储的位置。然后它会使用equals()方法来判断是否存在相同的元素。在这里,RectObject类重写的equals()方法总是返回false,意味着所有的RectObject对象都被视为不相等。因此,尽管r1、r2和r3的坐标值相同,但由于它们的哈希码不同,HashSet将把它们视为不同的元素,并将它们都成功添加到集合中。同时,由于HashSet不允许重复元素,重复添加的r1也会被忽略。最终,HashSet中包含了四个元素:r1、r2、r3和第二次添加的r1。因此,输出"size:4"。
而以下代码会输出 size:3
class RectObject {public int x;public int y;public RectObject(int x, int y) {this.x = x;this.y = y;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != Class())return false;final RectObject other = (RectObject) obj;if (x != other.x) {return false;}if (y != other.y) {return false;}return true;}
}
public class Example {public static void main(String[] args) {HashSet<RectObject> set = new HashSet<RectObject>();RectObject r1 = new RectObject(3, 3);RectObject r2 = new RectObject(5, 5);RectObject r3 = new RectObject(3, 3);set.add(r1);set.add(r2);set.add(r3);set.add(r1);System.out.println("size:" + set.size());}
}
解释说明: 在RectObject
类中,equals()
方法被重写为比较两个对象的x
和y
坐标是否相等。如果两个对象的坐标相等,则被视为相等。set.add(r1)
、set.add(r2)
和set.add(r3)
分别将对象r1
、r2
和r3
添加到HashSet
集合中。由于r1
和r3
具有相同的坐标值(都是(3, 3)),根据equals()
方法的实现,它们被视为相等的对象,所以只会添加一个到集合中。因此,最终输出的结果是"size:3",表示集合中有3个不同的RectObject
对象。需要注意的是,HashSet
类在判断对象是否相等时首先会调用hashCode()
方法,以确定对象属于哪个槽位,然后再调用equals()
方法进行进一步的比较。在这段代码中,由于没有重写hashCode()
方法,HashSet
会使用默认的hashCode()
实现,它基于对象的内存地址生成哈希码。因此,即使两个对象具有相同的坐标值,它们的哈希码由于不同的内存地址而不同,不会被视为相等的对象。但由于equals()
方法的实现,它们在HashSet
中会被认为是相等的。
接口是设计层面的概念,往往由设计师设计,将定义与实现分离
程序员实现接口,实现具体方法
面向接口编程的意思是指在面向对象的系统中所有的类或者模块之间的交互是由接口完成的
类实现接口,本质上与类继承类相似,区别在于“类最多只能继承一个类,即单继承,而一个类却可以同时实现多个接口”,多个接口用逗号隔开即可。实现类需要覆盖(重写)接口中的所有抽象方法,否则该类也必须声明为抽象类。接口是抽象的,接口中没有任何具体方法和变量,所以接口不能进行实例化
基本语法
try{可能会发生异常的代码//有return语句finally也会执行,除非强制关闭虚拟机(it(0))}catch(异常类型 引用名){异常处理代码}finally{最后都必须执行的代码}
异常与错误的区别:
异常是程序中发生的不正常事件流,通过处理程序依然可以运行下去。但是错误是无法控制的,程序肯定要中断
Error类和Exception类都是Throwable类的子类
例子
对以下两个代码片段说法正确的是?
代码片段1:int a = 3;int b = 0;int c = a / b;
代码片段2:
float a = 3.0f;
float b = 0.0f;
float c = a / b;
只有代码片段1抛出异常
解释说明: 代码1会抛出 ArithmeticException 算术异常,原因是在整数除法操作中,除数为0会引发该异常。而代码2,在Java中,浮点数除以0不会引发异常
,而是会返回一个特殊的值Infinity(正无穷大)、-Infinity(负无穷大)或NaN(不是一个数字)。当变量b被赋值为0.0f时,执行a / b操作时会得到正无穷大(Infinity)的结果。所以,变量c将被赋值为正无穷大。请注意,Java中整数(取余)取模0会得到一个结果为0的整数,而浮点数取模0则会引发异常,任何包含NaN的比较操作都会返回false
throws声明异常,throw抛出异常
层层抛出异常
catch语句中,处理异常后,再次用throw抛出该异常对象
继续抛出异常的作用:使得调用该方法的方法,能够再一次捕获并处理异常
自定义异常
基本语法
public class 异常类名 extends Exception{public 异常类名(String msg){super(msg);//使用 super() 方法来调用 Exception 类的构造函数以确保 MyException 类继承了 Exception 类的所有属性和行为}}
示例
泛型类
Java 中的泛型是一种在编译时进行类型检查和类型安全的机制。它允许在定义类、接口或方法时使用参数化类型,以便在使用时指定具体的类型。泛型使得代码更加灵活、可重用,并提供了更好的类型安全性。
泛型的主要目的是在编译时捕获类型错误,以避免在运行时出现类型转换异常或其他类型相关的错误。
下面是一个使用泛型的简单示例:
public class Box<T> {private T content;public T getContent() {return content;}public void setContent(T content) {t = content;}
}
public class Main {public static void main(String[] args) {Box<Integer> integerBox = new Box<Integer>();integerBox.setContent(123);int value = Content();System.out.println(value); // 输出:123Box<String> stringBox = new Box<String>();stringBox.setContent("Hello");String strValue = Content();System.out.println(strValue); // 输出:"Hello"}
}
在这个示例中创建了一个名为 Box
的泛型类。通过在类名后面用尖括号 <>
指定泛型参数 T
,我们可以在类中使用这个参数作为类型的占位符。
在 Box
类中,我们使用了泛型参数 T
来定义 content
字段的类型,并在 getContent()
和 setContent()
方法的返回类型和参数类型中使用了泛型参数 T
。
在 main()
方法中,我们创建了两个 Box
对象,一个是 Box<Integer>
类型,另一个是 Box<String>
类型。这样就分别指定了 content
字段的类型为 Integer
和 String
。
通过使用泛型,我们可以在编译时确保内容的类型安全性。如果我们尝试将错误类型的值放入 Box
对象中,编译器将会提供错误提示。
总之,Java 中的泛型是一种在编译时进行类型检查和类型安全的机制。它允许在定义类、接口或方法时使用参数化类型,并在使用时指定具体的类型,从而提供更好的代码复用性和类型安全性。
泛型接口
与泛型类完全相同
Comparable接口是泛型接口
public interface Comparable<T> {
public boolean compareTo(T other);
}
Comparable 接口包含一个类型参数 T,该参数是一个实现 Comparable 的类可以与之比较的对象的类型。这意味着如果定义一个实现 Comparable 的类,比如 String,要声明它可与什么比较(通常是与它本身比较)
public class String implements Comparable<String> { ... }
泛型方法
注意: 是否拥有泛型方法,与其所在的类是否泛型没有关系。要定义泛型方法,只需将泛型参数列表置于返回值前。
下面是一个使用泛型方法的示例:
public class ArrayUtils {public static <T> void printArray(T[] array) {for (T element : array) {System.out.println(element);}}
}
public class Main {public static void main(String[] args) {Integer[] integerArray = {1, 2, 3, 4, 5};ArrayUtils.printArray(integerArray);String[] stringArray = {"Hello", "World"};ArrayUtils.printArray(stringArray);}
}
在这个示例中创建了一个名为 ArrayUtils
的工具类,并在其中定义了一个名为 printArray()
的泛型方法。
在泛型方法的定义中,我们使用尖括号 <T>
来指定泛型参数,并在方法参数和方法体中使用该泛型参数来表示方法的参数类型和变量类型。
在 printArray()
方法中,我们使用 for-each 循环遍历传入的数组,并将数组中的元素逐个打印出来。
在 main()
方法中,我们首先创建一个 Integer
类型的数组 integerArray
和一个 String
类型的数组 stringArray
。然后,分别调用 ArrayUtils
类中的 printArray()
方法,并传入对应的数组作为参数。
通过使用泛型方法,我们可以在编译时对传入的数组进行类型检查,并且避免了为不同类型的数组编写重复的打印方法。
总之,泛型方法是定义在类中的方法,使用泛型参数来增加方法的灵活性和通用性。它可以独立于类的泛型参数,使得方法可以接受不同类型的参数,并提供更好的代码复用性和类型安全性。
Collection 所有集合类的根接口,还是一个泛型接口,子接口有List、Set、Map、Queue(队列)等
List 的实现类及使用场景:
Set 的实现类及使用场景:
Map 的实现类及使用场景:
Map中保存的是键值对 Map<key,Value> ,Key值不允许重复,如果重复,则覆盖。常用方法有:
put(K key,V value)该方法可以将key和value存到Map对象
get(Object key)该方法可以根据key值返回对应的value
size()返回Map对象中键值对的数量
补充:session 的底层是 Map
Iterator 遍历集合的迭代接口
图中的虚线框表示接口,不能new,粗黑框表示类,可以new
ArrayList例子:
public class User {private String userNamepublic User(String userName) {super();this.userName = userName;}public String getUserName() {return userName;}……
public void setUserName(String userName){ this.userName = userName;}
}Test:
public class GenericsList {
public static void main(String[] args) {//创建用户对象User user=new User("张三");User user1=new User("李四"); //创建集合对象,存放用户对象List<User> userList=new ArrayList<User>(); //用泛型userList.add(user);userList.add(user1);
}
}
三种循环遍历
public static void main(String[] args) {// 创建泛型集合ArrayList<Book> list=new ArrayList<Book>();Book b1=new Book("JAVA",24);Book b2=new Book("C",54);//添加数据List.add(b1);List.add(b2);//获取数据System.out.(1));//for循环遍历for (int i = 0; i<list.size(); i++) {System.out.(i));}//增强for循环遍历for (Book book : list) {System.out.println(book);}//Iterator遍历Iterator<Book> iter=list.iterator();while(iter.hasNext()) {System.out.());}}
以下例子主要解释说明:使用 array.add(0, stu1) 将 stu1 添加到集合的索引位置0上,然后使用 array.add(0, stu2) 将 stu2 添加到索引位置0上。这样做会导致 stu2 存储在索引位置0上,而 stu1 存储在索引位置1上,因为后添加的对象会将已有的对象后移。
public class ArrayListTest {public static void main(String[] args) {List<Student> array = new ArrayList<Student>();// 创建集合存放学生对象Student stu1 = new Student();// 创建学生对象stu1.setName("王星");Student stu2 = new Student();// 创建学生对象stu2.setName("王依");array.add(0, stu1);// 添加stu1对象array.add(0, stu2);// 添加stu2对象System.out.println(array.size());// 打印集合中的元素个数ve(0);// 移除索引位置0上的元素System.out.println(array.size());// 打印集合中的元素个数ve(stu1);//根据对象移除集合中的元素System.out.println(array.size());// 打印集合中的元素个数}
}
模拟登录判断例子:
package com.st;import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;import com.cwl.study.User;public class LoginTest {public static void main(String[] args) {//创建集合存放对象List<User> userlist=new ArrayList<User>();User user1=new User("小明",123456);User user2=new User("小陈",123456);User user3=new User("小刚",123456);User user4=new User("小哈",654321);userlist.add(user1);userlist.add(user2);userlist.add(user3);userlist.add(user4);// 创建控制台输入对象Scanner sc=new Scanner(System.in);int m=0;while(m<3) {System.out.println("请输入用户名:");String nowuser(); //接收控制台用户名System.out.println("请输入密码:");int nowpwdInt(); //接收控制台密码,是基本数据类型for (int i = 0; i < userlist.size(); i++) {User indexuser(i);// 判断用户名密码是否正确,注意基本数据类型的比较只能用==而不能用equalsif (nowuser.UserName())&&nowpwd=UserPassword()) {System.out.println("登录成功");return;} }System.out.println("用户名或密码错误!请重新输入:");m++;if (m==3) {System.out.println("三次机会已用完,请稍后再试");}}}
}
Java 中的基本数据类型不能直接调用对象方法,包括 equals
方法。基本数据类型有其对应的包装类(wrapper class),比如 int
对应的包装类是 Integer
。包装类是对象,可以调用对象方法,例如 equals
方法。如果想要比较两个基本数据类型的值是否相等,应该使用双等号(==
)进行比较,而不是使用 equals
方法。例如,使用 ==
进行比较两个 int
类型的值:
int num1 = 5;
int num2 = 5;
if (num1 == num2) {// 逻辑处理
}
如果需要使用 equals
方法比较两个基本数据类型的值,需要先将其转换为对应的包装类对象,再进行比较。
int num1 = 5;
int num2 = 5;
Integer wrapperNum1 = Integer.valueOf(num1); // 将 int 转换为 Integer
Integer wrapperNum2 = Integer.valueOf(num2);
if (wrapperNum1.equals(wrapperNum2)) {// 逻辑处理
}
请注意,在代码中尽量避免将基本数据类型用作对象来进行比较,直接使用 ==
进行比较更为高效和简洁。
hashSet和TreeSet的使用和区别:
Humans实体类
//public class Humans{} //HashSet
public class Humans implements Comparable<Humans>{ //TreeSetprivate String name;private String sex;private Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Humans(String name, String sex, Integer age) {super();this.name = name;this.sex = sex;this.age = age;}public Humans() {super();// TODO Auto-generated constructor stub}@Overridepublic String toString() {return "Humans [name=" + name + ", sex=" + sex + ", age=" + age + "]";}@Overridepublic int hashCode() {return Objects.hash(age, name, sex);}//重写equals方法去重,不重写则默认是进行虚地址比较,重写则是对属性进行比较@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != Class())return false;Humans other = (Humans) obj;return Objects.equals(age, other.age) && Objects.equals(name, other.name) && Objects.equals(sex, other.sex);}//重写compareTo方法,实现降序排序等@Overridepublic int compareTo(Humans h) {if (age>h.age) {return -1;} else if(age<h.age){return 1;}else {return 0;}}
}
测试类
public class HumanTest {public static void main(String[] args) {
// Set<Humans> humanSet=new HashSet<Humans>(); //HashSet需要重写equals方法
// Humans humans1=new Humans("小明","男",18);
// Humans humans2=new Humans("小陈","女",20);
// Humans humans3=new Humans("小明","男",18);
//
// humanSet.add(humans1);
// humanSet.add(humans2);
// humanSet.add(humans3);
//
// for (Humans humans : humanSet) {
// System.out.println(humans);
// }Set<Humans> humanSet=new TreeSet<Humans>(); //而TreeSet需要实现Comparable接口Humans humans1=new Humans("小明","男",18); //并重写compareTo方法,实现降序排序等逻辑,默认按照字典顺序排序Humans humans2=new Humans("小陈","女",20);Humans humans3=new Humans("小明","男",18);humanSet.add(humans1);humanSet.add(humans2);humanSet.add(humans3);for (Humans humans : humanSet) {System.out.println(humans);}}
}
HashMap和TreeMap的使用和区别:
HashMap中元素的key值不能重复,即彼此调用equals方法,返回为false。排列顺序是不固定的。
TreeMap中所有的元素都保持着某种固定的顺序,如果需要得到一个有序的Map就应该使用TreeMap,key值所在类必须实现Comparable接口。
HashMap的常用方法(TreeMap的也类似)
put<key,value> —>存放对象get(key); —>获取key所对应的value值的数据keySet() —> 返回此映射中所包含的键的 set 视图。
HashMap的例子:
import java.util.HashMap;public class HashMapTest {
public static void main(String[] args) {User user1=new User("王敏");User user2=new User("王辉");HashMap<String,User> map=new HashMap<String, User>();map.put(“001", user1);map.put(“002", user2);}
}
TreeMap的例子:
它适用于按自然顺序或自定义顺序遍历键(key)。TreeMap根据key值排序,key值(类)需要实现Comparable接口,实现compareTo方法。TreeMap根据compareTo的逻辑,对key进行排序。
ScortInfo实体类:
public class ScortInfo implements Comparable<ScortInfo> {private int num;
public int getNum() {return num;}
public void setNum(int num) {this.num = num;}
@Override
public int compareTo(ScortInfo o) {Return new Integer(this.num)Num());}
public ScortInfo(int num) {this.num = num;}
}测试类:
public class TreeMapTest {
public static void main(String[] args) {User user1=new User("王敏");User user2=new User("王辉");TreeMap<ScortInfo,User> tree=new TreeMap<ScortInfo,User>();tree.put(new ScortInfo(12), user1);tree.put(new ScortInfo(23), user2);
}
Properties类
Properites类是Hashtable类的子类,所以也间接地实现了Map接口。 在实际应用中,常使用Properties类对属性文件
进行处理。
常用方法:
load(); 加载文件;
getProperty(key); 通过key值获得对应的value值
setProperty(String key,String value) 给properties文件中写值。
小例子:
public class TestProperties {public static void main(String[] args) {Properties props = new Properties();try {props.load(new FileInputStream(new File("src/com/chinasofti/ch18/test.properties"))); //加载文件路径} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}System.out.Property("name")); //文件里的属性System.out.Property("password"));System.out.Property("age"));}
}
Collections类
是集合类的工具类,与数组的工具类Arrays类似,定义了大量静态方法,如排序方法
作用:
同步集合对象的方法
对List排序的方法
例子:
public class GenericsList {
public static void main(String[] args) {User user4= new User(11); // 创建用户对象User user1 = new User(14);User user2 = new User(13);User user3 = new User(9);List<User> userList = new ArrayList<User>(); // 创建集合对象,存放用户对象userList.add(user4);userList.add(user1);userList.add(user2);userList.add(user3);
for(User u:userList){System.out.UserAge());
}Collections.sort(userList);//调用排序方法实现升序排序//Collections.sort(userList, verseOrder()); //降序排序,排序后都存放在原集合中//System.out.println(userList);for(User u:userList){System.out.UserAge());}
}
运行结果
排序前:11,14,13,9
排序后:9,11,13,14
输入: 把数据读到内存中,即input
,进行数据的read
操作
输出: 从内存往外部设备写数据,即output
,进行数据的write
操作
File类
它无法对文件里面的具体内容进行操作
示例:
package com.st;
import java.io.File;
import java.io.IOException;
import com.cwl.study.MyFileFilter;
import com.cwl.study.MyFilenameFilter;public class FileTest {public static void main(String[] args) throws IOException {// 创建文件对象, 路径要么用“反斜杠/”,要么用“双斜杠\”File file=new File("D:/");ateNewFile();//创建目录对象File dir=new File("D:\java\test");dir.mkdir();//List()方法遍历所有文件对象名String[] fileName=dir.list();for (String name : fileName) {System.out.println(name);}//ListFiles()方法遍历所有对象File[] files=dir.listFiles();for (File f : files) {System.out.AbsolutePath());}//文件名过滤器//List(FilenameFilter)方法遍历符合过滤条件的名字(如以.txt结尾的文件)//需要新建类(MyFilenameFilter)实现FilenameFilter接口,并重写accept方法String[] fileName2=dir.list(new MyFilenameFilter());for (String f2 : fileName2) {System.out.println(f2);}//List(FileFilter)方法遍历符合过滤条件的名字,传的是文件//同样需要实现接口FileFilter并重写accept方法File[] file2=dir.listFiles(new MyFileFilter());for (File f2 : file2) {System.out.AbsolutePath());}}
}
//新建类实现接口
package com.cwl.study;
import java.io.File;
import java.io.FilenameFilter;public class MyFilenameFilter implements FilenameFilter {@Overridepublic boolean accept(File dir, String name) {if (dsWith(".txt")) {//过滤只要.txt结尾的文件return true;} else {return false;}}
}
运行结果如下图:
Swing组件入门,java也可以写前端页面
内部类: 就是类中类,要访问外部类的属性和方法时使用
匿名内部类: 没有类名,一般定义在外部类的某个方法中,使用匿名类来实现事件处理,会使代码更简洁,更灵活。只能使用一次,使用匿名内部类的前提条件是必须继承一个父类或实现一个接口。
务必理解透以下例子:
package com.cwl.study;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;public class Chat {//声明需要的组件private JFrame frame;private JTextField input;private JTextArea output;private JButton send,quit,clear;//对组件进行初始化public Chat() {frame=new JFrame();input=new JTextField();output=new JTextArea();send= new JButton("发送");quit=new JButton("取消");clear=new JButton("清空");frame.setTitle("欢迎使用聊天框~");frame.setSize(400,300);frame.setVisible(true);}//进行布局public void init() {//创建面板并布局JPanel panel=new JPanel();panel.setLayout(new FlowLayout());panel.add(send);panel.add(quit);panel.add(clear);Container ContainerPaneContentPane();ContainerPane.setBackground(Color.blue);ContainerPane.setLayout(new BorderLayout());ContainerPane.add(input,BorderLayout.SOUTH);ContainerPane.add(output,BorderLayout.CENTER);ContainerPane.add(panel,BorderLayout.EAST);// 注册/关联监听器quit.addActionListener(new ChatListener());send.addActionListener(new SendLinstener());clear.addActionListener(new ActionListener() { //使用匿名内部类@Overridepublic void actionPerformed(ActionEvent e) {output.setText("");}});}//使用内部类访问外部类的属性public class SendLinstener implements ActionListener {@Overridepublic void actionPerformed(ActionEvent e) {String inputmsgText();output.append("我:"+inputmsg+"n"); //追加内容input.setText(""); //清空内容}}//以下内部类可以使用匿名内部类代替,就不用写类名
// public class ClearLinstener implements ActionListener {
//
// @Override
// public void actionPerformed(ActionEvent e) {
// output.setText(""); //清空内容
// }
// }public static void main(String[] args) {Chat chat=new Chat();chat.init();}
}
实现退出监听器接口
package com.cwl.study;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;public class ChatListener implements ActionListener {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("聊天已退出");it(0); // 退出/关闭}
}
继承Thread类:
定义:
public class MyThread extends Thread {public void run() { //重写……}
}调用:
MyThread thread = new MyThread();
thread.start(); //启动线程,只能启动一次,多次启动会抛出异常
实现Runnable接口:
定义:
public class MyThread implements Runnable{@Overridepublic void run() {……}
}调用:
MyThread r = new MyThread();
Thread thread = new Thread(r); //创建一个线程作为外壳,将r包起来
thread.start();
两者区别:Runnable接口主要是为了解决Java中不允许多继承的问题,可以继承其他类的方法和属性
线程优先级用整数表示,取值范围是1~10,数值越大优先级越高,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级;
同步方法 –-使用synchronized
修饰的方法:
访问修饰符 synchronized 数据返回类型 方法名(){ … }
它锁定的是调用这个同步方法的对象。其它线程不能同时访问这个对象中任何一个synchronized方法。
同步语句块 –-只对这个区块的资源实行互斥访问:
synchronized(共享对象名)
{被同步的代码段
}
它锁定的是共享对象名对应的当前对象。线程中实现同步块一般是在run方法中添加。
线程同步注意事项:
不要对线程安全类的所有方法都进行同步,只对那些会改变共享资源方法的进行同步。同步块越大,多线程的效率越低
synchronized关键字可以修饰方法,也可以修饰代码块,但不能修饰构造器、抽象方法、成员属性等
synchronized关键字是不能继承的
无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁。
toString方法是在使用对象的时候被调用执行的
有三个主要方法:
前两个方法要和synchronized一起使用
务必理解透以下订票例子:
车票实体
package com.cwl.study.ticket;import java.util.Objects;//车票
public class TrainTicket {private String trainNo;private String seatNo;private String date;public String getTrainNo() {return trainNo;}public void setTrainNo(String trainNo) {ainNo = trainNo;}public String getSeatNo() {return seatNo;}public void setSeatNo(String seatNo) {this.seatNo = seatNo;}public String getDate() {return date;}public void setDate(String date) {this.date = date;}public TrainTicket(String trainNo, String seatNo, String date) {super();ainNo = trainNo;this.seatNo = seatNo;this.date = date;}public TrainTicket() {super();// TODO Auto-generated constructor stub}@Overridepublic String toString() {return "TrainTicket [trainNo=" + trainNo + ", seatNo=" + seatNo + ", date=" + date + "]";}@Overridepublic int hashCode() {return Objects.hash(date, seatNo, trainNo);}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != Class())return false;TrainTicket other = (TrainTicket) obj;return Objects.equals(date, other.date) && Objects.equals(seatNo, other.seatNo)&& Objects.equals(trainNo, ainNo);}
}
车票售卖后台/窗口实体
package com.cwl.study.ticket;
import java.util.ArrayList;//车票售卖后台/窗口
public class TicketSeller {private static ArrayList<TrainTicket> pool = new ArrayList<TrainTicket>();static {pool.add(new TrainTicket("G复兴号", "520", "2023.8.1"));pool.add(new TrainTicket("G复兴号", "521", "2023.8.1"));pool.add(new TrainTicket("G复兴号", "522", "2023.8.1"));pool.add(new TrainTicket("G复兴号", "523", "2023.8.1"));pool.add(new TrainTicket("G复兴号", "524", "2023.8.1"));}public static TrainTicket sellTicket(TrainTicket ticket) { // 卖票TrainTicket t = null;synchronized (ticket) { // 进行同步,加锁保护票for (TrainTicket t1 : pool) {if (t1.equals(ticket)) {t = t1;System.out.println("正在出票+" + t1);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}ve(t1);break;}}}return t;}public static void returnTicket(TrainTicket ticket) { // 退票synchronized (ticket) {pool.add(ticket);}}
}
乘客实体
package com.cwl.study.ticket;//乘客
public class Passenger {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public Passenger(String name) {super();this.name = name;}public Passenger() {super();}public void buyTicket(TrainTicket ticket) { //买票System.out.println("乘客" + name + "打算买票,信息为:" + ticket);Thread t = new Thread(new BuyTicketThread(this, ticket));t.start();}public void returnTicket(TrainTicket ticket) { //退票System.out.println("乘客" + name + "打算退票,信息为:" + ticket);Thread t = new Thread(new ReturnTicketThread(this, ticket));t.start();}@Overridepublic String toString() {return "Passenger [name=" + name + "]";}
}
买票线程
package com.cwl.study.ticket;//买票线程
public class BuyTicketThread implements Runnable {private Passenger passenger;private TrainTicket ticket;public BuyTicketThread(Passenger passenger, TrainTicket ticket) {super();this.passenger = passenger;this.ticket = ticket;}@Overridepublic void run() {synchronized (ticket) {TrainTicket t = TicketSeller.sellTicket(ticket);while (t == null) {System.out.Name() + ",很抱歉,票已经售出,请等待候补。");try {ticket.wait(); //使该线程进入等待状态} catch (InterruptedException e) {e.printStackTrace();}}if (t != null) {System.out.Name() + "购票成功,信息为:" + ticket);}}}
}
退票线程
package com.cwl.study.ticket;public class ReturnTicketThread implements Runnable {private Passenger passenger;private TrainTicket ticket;public ReturnTicketThread(Passenger passenger, TrainTicket ticket) {super();this.passenger = passenger;this.ticket = ticket;}@Overridepublic void run() {synchronized (ticket) {urnTicket(ticket);System.out.println("乘客" + Name() + "正在退票中...");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}ifyAll(); //唤醒其他所有线程(通知)System.out.println("乘客" + Name() + "退票成功,信息为:" + ticket);}}
}
测试类
package com.st;import com.cwl.study.ticket.Passenger;
import com.cwl.study.ticket.TrainTicket;public class TicketTest {public static void main(String[] args) {Passenger p1 = new Passenger("小陈");Passenger p2 = new Passenger("石敏");TrainTicket ticket1 = new TrainTicket("G复兴号", "520", "2023.8.1");TrainTicket ticket2 = new TrainTicket("G复兴号", "521", "2023.8.1");p1.buyTicket(ticket1);p2.buyTicket(ticket1);p1.returnTicket(ticket1);}
}
实现线程安全方法如下:
这两个是IOC的底层
反射: 可以通过类所在的路径动态地加载类中的所有信息(属性、方法、构造方法)
Class cla=Class.forName("com.cwl.study.Animal");//包含当前类的所有信息的字节码对象,注意C是大写//通过字节码对象来获取类中的属性、方法、构造方法Field[] fsDeclaredFields(); //获取属性for (Field field : fs) {System.out.println(field);}Method[] msDeclaredMethods(); //获取方法for (Method method : ms) {System.out.println(method);}Constructor[] csDeclaredConstructors(); //获取构造方法for (Constructor constructor : cs) {System.out.println(constructor);}
要使用Class类的方法,必须先获得Class类的实例,获得Class类实例的常用方法有如下三个:
示例说明:
String s="hello";//使用对象名获得Class实例
Class clazz1Class();//使用类名获得Class实例,类名必须是常量
Class clazz2=String.class;try {
//使用类名获得Class实例,类名可以是变量Class clazz3=Class.forName("java.lang.String");
} catch (ClassNotFoundException e)e.printStackTrace();
}
内省: 在字节码对象之中直接获取属性以及对应的setter等方法
BeanInfo biClass(), Class().getSuperclass());PropertyDescriptor[] pdsPropertyDescriptors(); //拿到所有的属性详情//遍历输出显示属性名、类型、方法for (PropertyDescriptor propertyDescriptor : pds) {System.out.Name());System.out.PropertyType().getCanonicalName());System.out.WriteMethod());}
Spring运用了很多设计模式: IOC容器应用工厂设计模式、Bean的管理应用单例设计模式、AOP切面编程应用代理设计模式、HandlerAdapter应用适配器设计模式、不同的HandlerMapping找寻对应的Handler应用策略设计模式、参数的获取以及转换AgurmentResovler应用责任链设计模式、监听器应用了观察者设计模式等
概念:指的是一个类只能有一个实例,这样的类被称为单例类,或者单态类,即Singleton Class;Bean就默认为单例模式
单例类的特点:
常见两种实现方式:
该类中包括:
构造方法是private
权限,保证其他类无法创建该类实例,只能该类自身创建
声明一个static
修饰的自身实例,保证该实例永远只是一个
提供一个public
方法,返回定义的static自身实例
适配器模式使原本无法在一起工作的两个类能够在一起工作,有一个中间类(变压器)
Spring AOP的增强或通知使用到了适配器模式,Spring MVC中也是用到了适配器模式适配不同的Controller;
常见两种实现方式:
类形式涉及的成员:
类形式适配器代码示例: Adapter继承
Adaptee再实现
Target
目标类:
package com.shi;public interface Target { //接口void eat(); //与源类Adaptee中的方法相同void buy(); //目标类中的新方法
}
源类:
package com.shi;
//源类
public class Adaptee {public void eat() {System.out.println("调用了源类Adaptee的eat方法");}
}
适配器类:
package com.shi;
//适配器类
public class Adapter extends Adaptee implements Target { //先继承再实现public void buy() {System.out.println("调用了适配器Adapter的buy方法");}
}
使用/测试类:
package com.shi;
//使用/测试类
public class TestMoShi {public static void request(Target t) {t.eat();t.buy();}public static void main(String[] args) {request(new Adapter());}
}
最后输出为:
调用了源类Adaptee的eat方法
调用了适配器Adapter的buy方法
实例形式涉及的成员:
实例形式适配器代码示例:
目标类:
package com.shi;public interface Target { //接口void eat(); //与源类Adaptee中的方法相同void buy(); //目标类中的新方法
}
源类:
package com.shi;
//源类
public class Adaptee {public void eat() {System.out.println("调用了源类Adaptee的eat方法");}
}
适配器类:
package com.shi;
//适配器类
public class Adapter implements Target {private Adaptee adaptee; //关联Adapteepublic Adapter(Adaptee adaptee) {super();this.adaptee = adaptee;}//关联后可以直接调用Adaptee中的方法了public void eat() {adaptee.eat();}//覆盖Targer中的方法public void buy() {System.out.println("调用了适配器Adapter的buy方法");}
}
使用/测试类:
package com.shi;
//使用/测试类
public class TestMoShi {public static void request(Target t) {t.eat();t.buy();}public static void main(String[] args) {request(new Adapter(new Adaptee()));}
}
//输出结果和上面一样
代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一个代理类来控制对另一个对象的访问。代理对象可以充当目标对象的接口,以便在不改变客户端代码的情况下,对目标对象进行间接访问和增加额外功能。
代理模式主要包含以下角色:
又分为静态代理和动态代理,静态代理的代理类在编译期生成,而动态代理的代理类在运行时动态生成。动态代理又有JDK代理和CGLib代理两种。静态代理的弊端:代理者做代理的类型单一,代理的方法单一;JDK动态代理的弊端:必须依赖接口,从而有更强的CGLIB代理不依赖接口;Spring的AOP功能就用到了JDK的动态代理和CGLIB代理
例子1:
// 定义一个接口,表示远程服务
interface RemoteService {void doSomething();
}// 实现远程服务的具体类
class RemoteServiceImpl implements RemoteService {@Overridepublic void doSomething() {System.out.println("执行远程服务的操作");}
}// 定义代理类,充当远程服务的本地代表
class RemoteServiceProxy implements RemoteService {private RemoteService remoteService;public RemoteServiceProxy() {// 在代理类的构造函数中实例化远程服务对象remoteService = new RemoteServiceImpl();}@Overridepublic void doSomething() {// 在调用远程服务之前执行额外的逻辑System.out.println("执行一些额外的操作");// 调用远程服务的方法remoteService.doSomething();// 在调用远程服务之后执行额外的逻辑System.out.println("执行一些其他操作");}
}public class ProxyPatternExample {public static void main(String[] args) {// 使用代理对象调用远程服务RemoteService proxy = new RemoteServiceProxy();proxy.doSomething();}
}
输出结果:
执行一些额外的操作
执行远程服务的操作
执行一些其他操作
在上述示例中,RemoteService接口表示远程服务,RemoteServiceImpl是具体的远程服务实现类。RemoteServiceProxy作为代理类,实现了RemoteService接口,并在调用远程服务的方法之前和之后执行了一些额外的操作。在main方法中,通过代理对象调用远程服务的方法,代理对象会在调用之前和之后执行额外的逻辑。
例子2:
//火车站的售票窗口 被代理人
public class Station implements Sell {public void sellTicket(){System.out.println("售出了票!赚钱了");}
}
public interface Sell {public void sellTicket();
}
//黄牛 代理人
public class HuangNiu implements Sell {private Station s;public void sellTicket(){System.out.println("将人引到售票窗口");s.sellTicket();System.out.println("抽成!赚到了钱");}public HuangNiu(Station s) {super();this.s = s;}
}
public class Test {public static void main(String[] args) {Station s= new Station();HuangNiu hn=new HuangNiu(s);hn.sellTicket();}
}
最后运行结果:
将人引到售票窗口
售出了票!赚钱了
抽成!赚到了钱
静态代理的弊端:代理者做代理的类型单一,代理的方法单一(黄牛不能再为大麦网买票)
请律师例子:
//人
public class Person implements Lawsuit {public void justify() {// TODO Auto-generated method stubSystem.out.println("我的情况是。。。。。");}
}
//金牌代理律师
public class GoldProxy implements InvocationHandler {private Object obj; //所有类型@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable { //泛指所有方法System.out.println("111");method.invoke(obj, args);System.out.println("222");return null;}public GoldProxy(Object obj) {super();this.obj = obj;}
}
//官司
public interface Lawsuit { public void justify(); //辩解
}
另加测试的大麦网:
public class Dmw implements Sell {public void sellTicket() {System.out.println("大麦网卖出去了周杰伦的票!");}
}
测试类:
public class Test2 {public static void main(String[] args) {Person p= new Person(); //声明一个人GoldProxy gp= new GoldProxy(p); //请一个律师//建立合同Lawsuit ls=(Class().getClassLoader(), p.getClass().getInterfaces(),gp);ls.justify();//换大麦网也可以
/* Dmw d=new Dmw();GoldProxy gp= new GoldProxy(d);Sell s=(Sell) Class().getClassLoader(), d.getClass().getInterfaces(), gp);s.sellTicket();*/}
}
JDK动态代理的弊端:必须依赖接口,从而有更强的CGLIB代理不依赖接口
Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
主要思想:有实时监听,这边有改动,另外一边也会有相应的改变
Spring事件驱动模型就是观察者模式的一个经典应用;Spring事件驱动模型是Spring框架中的一种编程模型,也被称为发布/订阅模型,通过使用观察者模式和事件机制,实现了组件之间基于事件的解耦和通信
System.currentTimeMillis() 是Java中的一个方法,它用于获取当前系统时间,以毫秒为单位,这个方法的返回值表示当前计算机时间和GMT时间(格林威治时间)
调用该方法获取到的时间戳的值可以用在各种场景下,例如:
java数据类型有哪些以及分别占的字节是多少?
分为基本数据类型和引用数据类型
比特位是计算机存储设备的最小信息单元,操作系统分配内存最少一个1个字节
1字节(byte)=8比特(bit)
各类型的默认值:
自动装箱(boxing): 基本数据类型——>包装器类型,例如int型转为integer型
自动拆箱(unboxing): 包装器类型——>基本数据类型
各类型的默认值作用是占位置占内存
面试题10.0/2
在Java中,当进行除法运算时,如果参与运算的两个操作数都是整数类型,那么结果也会是整数类型。这意味着小数部分会被舍弃,只保留整数部分。但是,如果其中一个操作数是浮点数类型(如10.0),那么结果也会是浮点数类型。
因此,10.0/2的结果是5.0,因为其中一个操作数是浮点数10.0,所以结果也是浮点数类型,并保留了小数部分。
JDK1.8中,HashMap采用数组+链表+红黑树(一种平衡搜索二叉树)实现,当链表长度超过阈值(8)时,将链表转换为红黑树
移位符>>或<<
5>>2就是1——0401
字符串常量池问题
java之所以是跨平台的,是因为在不同的平台上有不同的虚拟机,java文件通过虚拟机执行,虚拟机再与操作系统交互,操作系统再与硬件打交道。
JVM组成: 类加载器、运行时数据区、执行引擎、本地接口
方法执行的时候会创建栈帧并入栈,方法结束则弹栈(出栈)
负责将类的字节码加载到内存中,并将其转换为可执行的Java对象
类加载器的工作原理/过程:
可简化为:
类加载器采用了双亲委派机制
作用:避免类的重复加载;保证类加载的安全性
对象的产生过程:
字符串常量池:
equal和==的区别:
注意 基本数据类型的比较只能用==而不能用equals
public static void main(String[] args) {//intern方法用于返回字符串常量池的地址,如果字符串常量池中没有则返回堆中的地址String str=new String("qwer"); //堆中地址String str1=str.intern(); //字符串常量池中地址String str2=str.intern(); //字符串常量池中地址System.out.println(str==str1); //falseSystem.out.println(str1==str2); //true}
public class Test4 {public static void main(String[] args) {String str="qwer";//返回常量池地址String str2="qw"+"er";//在编译期合并"qwer"String str3=new String("qwer");//返回堆中地址String str4="qw"+new String("er");//new String("qwer") 返回堆中地址String str5="qw";String str6=str5+"er";//加法的两边出现了对象的引用,无法在编译期确定其值,因此无法合并String str7=new String("er");String str8=str5+str7;//new String("qwer") 返回堆中地址System.out.println(str==str2);//tSystem.out.println(str==str3);//fSystem.out.println(str==str4);//fSystem.out.println(str3==str4);//fSystem.out.println(str==str6);//fSystem.out.println(str==str8);//fSystem.out.println(str3==str8);//f}
}
public class Test5 {public static void main(String[] args) {String str="qwer";final String str1="qw";//final修饰的是常量,其值不会再改变String str2=str1+"er";//+此时完全可以确定两边的值都是已经不会变的,因此可以合并System.out.println(str==str2);}
}
public class Test6 {public static void main(String[] args) {String str1="qwer";final String str2=getMsg();//getMsg必须要运行才能返回qw,但这个时候不是编译期,是运行期String str3=str2+"er";//+只能在编译期合并System.out.println(str1==str3);}public static String getMsg() {return "qw";}
}
垃圾收集器在对堆进行垃圾回收时,首先要判断哪些对象还活着,哪些对象已死(即不被任何途径引用的对象)
引用: 在JDK1.2之前,引用被定义为当一个reference类型的数据代表的是另外一块内存的起始地址,该类型的数据被称为引用;但对于一些“食之无味,弃之可惜”的对象就显得无能为力,因此在JDK1.2之后引用又分为强引用、软引用、弱引用、虚引用,强度依次递减。
垃圾回收常用的算法
引用计数器算法(Reference Counting):
标记清除算法(Mark and Sweep):
复制算法:
工作步骤如下:
有效地解决内存碎片的问题,避免了内存分配和回收过程中的不连续空间的产生。复制算法可以通过简单的内存拷贝操作来完成,具有较高的效率。但是,复制算法的缺点是需要额外的内存空间用于存储复制的对象,并且在对象存活率较高时,复制的开销可能会比较大。因此,复制算法通常用于新生代的垃圾回收,而不是整个堆内存的回收。
分代算法(现代普遍使用):
分代算法的基本思想是根据对象的生命周期来决定垃圾回收的策略,具体步骤如下:
分代算法的核心思想是根据对象的生命周期将堆内存分为不同的区域,并针对不同的区域采取不同的垃圾回收策略。通过这种方式,可以提高垃圾回收的效率和性能。年轻代中的垃圾回收频率较高,而老年代中的垃圾回收频率较低,这样可以减少全堆垃圾回收的次数,提高系统的响应速度。
Serial 收集器
Serial 垃圾收集器是一种单线程的、串行执行的垃圾收集器,属于 HotSpot 虚拟机的默认选择之一。它主要用于低端设备和客户端应用程序中,对于小型堆空间来说效果较好。
Serial 垃圾收集器的特点如下:
单线程: Serial 垃圾收集器使用单线程进行垃圾收集工作,即只有一个线程用于执行垃圾回收操作。它通过暂停应用程序的所有用户线程(STW),然后进行垃圾回收。因为只有一个线程在工作,所以不会产生线程同步的开销。
复制算法: Serial 垃圾收集器主要用于新生代的垃圾回收,采用复制算法进行回收。
暂停应用: 由于 Serial 垃圾收集器使用单线程进行垃圾回收,它在执行垃圾回收时需要暂停应用程序的所有用户线程。这个暂停时间是可见的,会导致应用程序出现较长的停顿,适用于对响应时间要求不高的场景。
低延迟: 由于 Serial 垃圾收集器是单线程的,它的垃圾回收工作在后台进行,不会占用过多的系统资源。这使得它适合于低端设备和客户端应用程序,对系统的响应时间没有太大影响。
总结来说,Serial 垃圾收集器是一种简单而高效的垃圾收集器,适用于对于资源受限、对响应时间要求不高的场景。然而它无法充分利用多核 CPU 的优势,并且停顿时间较长,不适合用于服务器端和大型应用程序中。
ParNew 收集器
ParNew 垃圾收集器是 HotSpot 虚拟机中的一种多线程、并行的垃圾收集器,它主要用于新生代的垃圾回收,是 Serial 垃圾收集器的多线程版本。
ParNew 垃圾收集器的特点如下:
多线程并行: ParNew 垃圾收集器使用多个线程并行执行垃圾回收操作,可以充分利用多核 CPU 的优势来提高垃圾回收的效率。它在执行垃圾回收时,与应用程序的用户线程并发执行,减少了垃圾回收对应用程序的停顿时间。
复制算法: ParNew 垃圾收集器同样采用复制算法来进行新生代的垃圾回收。
与 CMS 收集器配合使用: ParNew 垃圾收集器通常与 CMS (Concurrent Mark Sweep) 收集器配合使用,共同完成整个堆内存的垃圾回收。ParNew 收集器负责新生代的垃圾回收,而 CMS 收集器负责老年代的垃圾回收,两者可以并发执行,减少整体垃圾回收对应用程序的影响。
适用于多核服务器: 由于 ParNew 垃圾收集器采用多线程并行执行,能够充分利用多核 CPU 的优势,因此非常适合用于多核服务器上的大型应用程序。它可以在减少停顿时间的同时提高垃圾回收的效率。
需要注意的是,ParNew 垃圾收集器仅适用于新生代的垃圾回收,老年代的垃圾回收仍然需要使用其他的收集器。而且由于是并行执行的垃圾收集器,它的停顿时间可能会比 Serial 收集器稍长,但总体上仍然比较低延迟。同时,ParNew 垃圾收集器不支持压缩算法(标记-整理算法、复制算法、分代算法),因此无法进行空间整理。
Parallel Scavenge收集器
与ParNew 垃圾收集器不同的是,Parallel Scavenge的设计目标是达到一个可控制的吞吐量,即最大化程序的运行时间,并且减少垃圾回收的停顿时间,其他都和ParNew差不多。总的来说,它通过并行处理和自适应调节等技术手段,可以提高垃圾回收的效率和性能,达到较高的吞吐量。适用于对吞吐量要求较高的应用程序场景。
CMS收集器
CMS(Concurrent Mark Sweep)是一种并发垃圾收集器,用于进行老年代的垃圾回收。与其他垃圾收集器不同,CMS的设计目标是减少垃圾回收的停顿时间,以提高应用程序的响应性能。
CMS的主要特点如下:
并发标记: CMS使用并发标记算法进行垃圾回收。它在应用程序运行的同时,使用多个线程对堆内存中的对象进行标记。这样可以减少垃圾回收的停顿时间,提高应用程序的响应性能。
并发清除: 它在标记阶段完成后,并发地清除垃圾对象。
分代收集: CMS将堆内存分为年轻代和老年代两个部分。年轻代使用复制算法进行垃圾回收,而老年代使用标记-清除算法进行垃圾回收。通过分代的方式,可以根据对象的特性选择合适的垃圾回收算法,提高垃圾回收的效率。
低停顿时间: CMS的设计目标是减少垃圾回收的停顿时间,以提高应用程序的响应性能。它通过并发标记和并发清除等技术手段,可以在应用程序运行的同时进行垃圾回收,减少停顿时间。
内存碎片化: 由于CMS使用标记-清除算法进行垃圾回收,可能会导致内存碎片化问题。为了解决这个问题,CMS提供了一种叫做空闲列表(Free List) 的数据结构,用于管理内存碎片,以提高内存的利用率。
需要注意的是,CMS垃圾收集器在提供低停顿时间的同时,可能会牺牲一定的吞吐量。它适用于那些对于响应时间要求较高、对于停顿时间敏感的应用场景,如Web服务器、交易系统等。对于具有大量数据处理需求、更关注吞吐量的应用场景,可能需要考虑其他类型的垃圾收集器,如Parallel Scavenge或G1等。
G1收集器(目前效果最好)
G1(Garbage-First)是一种面向服务端应用程序的垃圾收集器,G1垃圾收集器在整体设计上与CMS垃圾收集器有相似之处,但它具有更高的吞吐量和更低的停顿时间。
G1的设计原则就是简单可行的性能调优
性能调优时的主要参数:XX:+UseG1GC、-Xmx32g、 -XX:MaxGCPauseMillis=200
其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis-=200设置GC的最大暂停时间为2Q0ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。
G1垃圾收集器的主要特点如下:
区域化内存管理:G1将整个堆内存划分为多个大小相等的区域(Region),每个区域可以是年轻代或老年代。这种区域化的内存管理方式使得G1可以更加灵活地进行垃圾回收,以满足不同应用程序的需求。
并发标记:G1使用并发标记算法进行垃圾回收。它在应用程序运行的同时,使用多个线程对堆内存中的对象进行标记。这样可以减少垃圾回收的停顿时间,提高应用程序的响应性能。
区域化的垃圾回收:G1使用增量式的、区域化的垃圾回收算法。它将堆内存划分为多个区域,并根据垃圾回收的情况动态地选择需要回收的区域。这样可以将垃圾回收的工作分摊到多个时间片段中,减少每次垃圾回收的停顿时间。
智能回收:G1垃圾收集器具有智能回收的能力。它可以根据应用程序的负载情况和垃圾回收的效果,动态地调整垃圾回收的参数,以达到最佳的性能和吞吐量。
低停顿时间:G1的设计目标是减少垃圾回收的停顿时间,以提高应用程序的响应性能。它通过并发标记和区域化的垃圾回收等技术手段,可以在应用程序运行的同时进行垃圾回收,减少停顿时间。
JVM相关参数:
要再花时间学习JVM调优!!!
JVM调优主要是通过调整Java虚拟机中的一些参数来优化Java应用程序的性能。常见调优方法:
事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可: 面试经常问
在实际开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行常见问题如下:
(面试经常问,一定要背熟再去理解为自己的话语,特别重要!!)
用户向服务器发送请求,请求被Spring前端控制ServeltDispatcherServlet捕获
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回
DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。执行相应的Controller(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。在填充Handler的入参过程中,根据配置,Spring将帮你做一些额外的工作:HttpMessageConveter
• 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;
• 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等;
• 数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等;
• 数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
Handler执行完成后,向DispatcherServlet返回一个ModelAndView对象
根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet
ViewResolver结合Model和View,来渲染视图
将渲染结果返回给客户端
总结概括为: 所有的请求交给dispatcherServlet,将请求解析通过HandlerMapping寻找对应的Handler,再次找到handler对应的HandlerAdapter(适配器),再通过参数的获取(argumentResolvers)以及转换交给对应的handler方法,紧接着执行,将得到的结果ModelAndView交给dispatcherServlet控制的viewResovler视图渲染器进行渲染,最后返回给用户。
图解:
都是用来解决并发问题 面试会问
面试会提问
Hibernate 把对象分为 4 种状态:
session 的特定方法能使对象从一个状态转换到另一个状态
4 种状态流程图:
注意以上的几种状态只是Hibernate认为的状态,与java无关,java一般只有两种状态:新建new和垃圾回收GC
持久化状态的快照功能演示:只有访问数据库后才建立快照
UserDetail pd = new UserDetail();
pd.setId(400011);
pd.setName("张三2");
//update将当前对象加入session缓存,进入持久态,但由于没有访问数据库,因此不具备快照功能。
session.update(pd);
//已经在缓存中,不执行查询
UserDetail pd1 = (UserDetail) (UserDetail.class, 400011);
System.out.Name());
//由于没有快照,因此提交时默认将会执行update语句
tsmit();
1 1 1 1
8 4 2 1
二进制——>十进制: 从最低位(右边)开始,分别提取每个位数*2^(位数-1)再相加求和
八转十:位数*8^(位数-1)再相加求和
十六转十:位数*16^(位数-1)再相加求和
0B 1101 1010 0001 = 3489
二进制——>八进制: 从最低位(右边)开始,每三位为一组,不够用0补,换算为八进制数再拼接
0B 1101 1010 0001 = 110 110 100 001 = 6641
二进制——>十六进制: 从最低位(右边)开始,每四位为一组,不够用0补,换算为十六进制数再拼接,10(A)——>15(F)
0B 1101 1010 0001 = 0X DA1
十进制——>二进制: 不断除二取余,直到商为零,从下到上的余数就是二进制
十进制——>八进制: 不断除八取余,直到商为零,从下到上的余数就是八进制
十进制——>十六进制: 不断除十六取余,直到商为零,从下到上的余数就是十六进制
原码、反码、补码
规则:
补充:
位运算符
规则:
<<(左移)运算符:将一个数的所有二进制位向左移动指定的位数,右边空出的位用0填充。移动n位相当于将数乘以2的n次方。
例如:
int a = 5; // 二进制表示为:0000 0101
int b = a << 2; // 将a左移2位
// b的二进制表示为:0001 0100,转换为十进制为20
System.out.println(b); // 输出:20
“>>”(右移)运算符:将一个数的所有二进制位向右移动指定的位数,左边空出的位用原来的最高位填充。移动n位相当于将数除以2的n次方并向下取整。
例如:
int a = 20; // 二进制表示为:0001 0100
int b = a >> 2; // 将a右移2位
// b的二进制表示为:0000 0101,转换为十进制为5
System.out.println(b); // 输出:5
“>>>”(无符号右移)运算符:将一个数的所有二进制位向右移动指定的位数,左边空出的位用0填充。不论原来的最高位是0还是1,都用0填充。
例如:
int a = -20; // 二进制表示为:1111 1111 1111 1111 1111 1111 1110 1100
int b = a >>> 2; // 将a无符号右移2位
// b的二进制表示为:0011 1111 1111 1111 1111 1111 1111 1011,转换为十进制为1073741827
System.out.println(b); // 输出:1073741827
需要注意的是,<<、>>和>>>运算符只能用于整数类型(byte、short、int和long),不能用于浮点数类型。
例子:将-10转换为二进制补码
-10的绝对值是10,将其转换为二进制数为 0000 1010。
反转二进制数的每一位得到 1111 0101。
对反转后的二进制数进行加1操作,得到最终结果 1111 0110。这就是十进制数-10的二进制补码表示。
本文发布于:2024-01-28 17:06:46,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17064328118943.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |